Skip to content

Commit 534da8a

Browse files
committed
Show an error in the menubar when gh isn't available
1 parent 40fd258 commit 534da8a

File tree

3 files changed

+131
-46
lines changed

3 files changed

+131
-46
lines changed

cmd/goose/github.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,22 @@ type prResult struct {
204204
// It returns GitHub data immediately and starts Turn API queries in the background (when waitForTurn=false),
205205
// or waits for Turn data to complete (when waitForTurn=true).
206206
func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incoming []PR, outgoing []PR, _ error) {
207+
// Check if we have a client
208+
if app.client == nil {
209+
return nil, nil, fmt.Errorf("no GitHub client available: %s", app.authError)
210+
}
211+
207212
// Use targetUser if specified, otherwise use authenticated user
208-
user := app.currentUser.GetLogin()
213+
user := ""
214+
if app.currentUser != nil {
215+
user = app.currentUser.GetLogin()
216+
}
209217
if app.targetUser != "" {
210218
user = app.targetUser
211219
}
220+
if user == "" {
221+
return nil, nil, errors.New("no user specified and current user not loaded")
222+
}
212223

213224
const perPage = 100
214225
opts := &github.SearchOptions{

cmd/goose/main.go

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,15 @@ type App struct {
115115
turnClient *turn.Client
116116
currentUser *github.User
117117
previousBlockedPRs map[string]bool
118-
notificationHistory map[string]NotificationState // Track state and notification time per PR
118+
notificationHistory map[string]NotificationState
119119
client *github.Client
120120
lastMenuState *MenuState
121121
targetUser string
122122
cacheDir string
123-
incoming []PR
123+
authError string
124124
outgoing []PR
125125
pendingTurnResults []TurnResult
126+
incoming []PR
126127
consecutiveFailures int
127128
updateInterval time.Duration
128129
mu sync.RWMutex
@@ -131,8 +132,54 @@ type App struct {
131132
noCache bool
132133
hideStaleIncoming bool
133134
loadingTurnData bool
134-
enableReminders bool // Whether to send daily reminder notifications
135-
enableAudioCues bool // Whether to play audio cues for notifications
135+
enableReminders bool
136+
enableAudioCues bool
137+
}
138+
139+
func loadCurrentUser(ctx context.Context, app *App) {
140+
log.Println("Loading current user...")
141+
142+
if app.client == nil {
143+
log.Println("Skipping user load - no GitHub client available")
144+
return
145+
}
146+
147+
var user *github.User
148+
err := retry.Do(func() error {
149+
var retryErr error
150+
user, _, retryErr = app.client.Users.Get(ctx, "")
151+
if retryErr != nil {
152+
log.Printf("GitHub Users.Get failed (will retry): %v", retryErr)
153+
return retryErr
154+
}
155+
return nil
156+
},
157+
retry.Attempts(maxRetries),
158+
retry.DelayType(retry.BackOffDelay),
159+
retry.MaxDelay(maxRetryDelay),
160+
retry.OnRetry(func(n uint, err error) {
161+
log.Printf("GitHub Users.Get retry %d/%d: %v", n+1, maxRetries, err)
162+
}),
163+
retry.Context(ctx),
164+
)
165+
if err != nil {
166+
log.Printf("Warning: Failed to load current user after %d retries: %v", maxRetries, err)
167+
if app.authError == "" {
168+
app.authError = fmt.Sprintf("Failed to load user: %v", err)
169+
}
170+
return
171+
}
172+
173+
if user == nil {
174+
log.Print("Warning: GitHub API returned nil user")
175+
return
176+
}
177+
178+
app.currentUser = user
179+
// Log if we're using a different target user (sanitized)
180+
if app.targetUser != "" && app.targetUser != user.GetLogin() {
181+
log.Printf("Querying PRs for user '%s' instead of authenticated user", sanitizeForLog(app.targetUser))
182+
}
136183
}
137184

138185
func main() {
@@ -193,40 +240,13 @@ func main() {
193240
log.Println("Initializing GitHub clients...")
194241
err = app.initClients(ctx)
195242
if err != nil {
196-
log.Fatalf("Failed to initialize clients: %v", err)
197-
}
198-
199-
log.Println("Loading current user...")
200-
var user *github.User
201-
err = retry.Do(func() error {
202-
var retryErr error
203-
user, _, retryErr = app.client.Users.Get(ctx, "")
204-
if retryErr != nil {
205-
log.Printf("GitHub Users.Get failed (will retry): %v", retryErr)
206-
return retryErr
207-
}
208-
return nil
209-
},
210-
retry.Attempts(maxRetries),
211-
retry.DelayType(retry.BackOffDelay),
212-
retry.MaxDelay(maxRetryDelay),
213-
retry.OnRetry(func(n uint, err error) {
214-
log.Printf("GitHub Users.Get retry %d/%d: %v", n+1, maxRetries, err)
215-
}),
216-
retry.Context(ctx),
217-
)
218-
if err != nil {
219-
log.Fatalf("Failed to load current user after %d retries: %v", maxRetries, err)
220-
}
221-
if user == nil {
222-
log.Fatal("GitHub API returned nil user")
243+
log.Printf("Warning: Failed to initialize clients: %v", err)
244+
app.authError = err.Error()
245+
// Continue running with auth error - will show error in UI
223246
}
224-
app.currentUser = user
225247

226-
// Log if we're using a different target user (sanitized)
227-
if app.targetUser != "" && app.targetUser != user.GetLogin() {
228-
log.Printf("Querying PRs for user '%s' instead of authenticated user", sanitizeForLog(app.targetUser))
229-
}
248+
// Load current user if we have a client
249+
loadCurrentUser(ctx, app)
230250

231251
log.Println("Starting systray...")
232252
// Create a cancellable context for the application
@@ -242,16 +262,8 @@ func main() {
242262

243263
func (app *App) onReady(ctx context.Context) {
244264
log.Println("System tray ready")
245-
systray.SetTitle("Loading PRs...")
246265

247-
// Set tooltip based on whether we're using a custom user
248-
tooltip := "GitHub PR Monitor"
249-
if app.targetUser != "" {
250-
tooltip = fmt.Sprintf("GitHub PR Monitor - @%s", app.targetUser)
251-
}
252-
systray.SetTooltip(tooltip)
253-
254-
// Set up click handlers
266+
// Set up click handlers first (needed for both success and error states)
255267
systray.SetOnClick(func(menu systray.IMenu) {
256268
log.Println("Icon clicked")
257269
if menu != nil {
@@ -270,6 +282,26 @@ func (app *App) onReady(ctx context.Context) {
270282
}
271283
})
272284

285+
// Check if we have an auth error
286+
if app.authError != "" {
287+
systray.SetTitle("⚠️")
288+
systray.SetTooltip("GitHub PR Monitor - Authentication Error")
289+
// Create initial error menu
290+
app.rebuildMenu(ctx)
291+
// Clean old cache on startup
292+
app.cleanupOldCache()
293+
return
294+
}
295+
296+
systray.SetTitle("Loading PRs...")
297+
298+
// Set tooltip based on whether we're using a custom user
299+
tooltip := "GitHub PR Monitor"
300+
if app.targetUser != "" {
301+
tooltip = fmt.Sprintf("GitHub PR Monitor - @%s", app.targetUser)
302+
}
303+
systray.SetTooltip(tooltip)
304+
273305
// Clean old cache on startup
274306
app.cleanupOldCache()
275307

cmd/goose/ui.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,48 @@ func (app *App) rebuildMenu(ctx context.Context) {
226226
// Clear all existing menu items
227227
systray.ResetMenu()
228228

229+
// Check for auth error first
230+
if app.authError != "" {
231+
// Show authentication error message
232+
errorTitle := systray.AddMenuItem("⚠️ Authentication Error", "")
233+
errorTitle.Disable()
234+
235+
systray.AddSeparator()
236+
237+
// Add error details
238+
errorMsg := systray.AddMenuItem(app.authError, "Click to see setup instructions")
239+
errorMsg.Click(func() {
240+
if err := openURL(ctx, "https://cli.github.com/manual/gh_auth_login"); err != nil {
241+
log.Printf("failed to open setup instructions: %v", err)
242+
}
243+
})
244+
245+
systray.AddSeparator()
246+
247+
// Add setup instructions
248+
setupInstr := systray.AddMenuItem("To fix this issue:", "")
249+
setupInstr.Disable()
250+
251+
option1 := systray.AddMenuItem("1. Install GitHub CLI: brew install gh", "")
252+
option1.Disable()
253+
254+
option2 := systray.AddMenuItem("2. Run: gh auth login", "")
255+
option2.Disable()
256+
257+
option3 := systray.AddMenuItem("3. Or set GITHUB_TOKEN environment variable", "")
258+
option3.Disable()
259+
260+
systray.AddSeparator()
261+
262+
// Add quit option
263+
quitItem := systray.AddMenuItem("Quit", "")
264+
quitItem.Click(func() {
265+
systray.Quit()
266+
})
267+
268+
return
269+
}
270+
229271
// Update tray title
230272
app.setTrayTitle()
231273

0 commit comments

Comments
 (0)