@@ -60,6 +60,7 @@ func spinner(stop <-chan struct{}, done chan<- struct{}, label string) {
6060 case <- stop :
6161 spinnerActive .Store (0 )
6262 fmt .Print ("\r \033 [K" )
63+ notifySpinnerQuiet ()
6364 close (done )
6465 return
6566 default :
@@ -247,7 +248,12 @@ func processStreamEvent(e iteragent.Event, fullContent string, toolStart time.Ti
247248 case iteragent .EventContextCompacted :
248249 fmt .Printf ("\r \033 [K%s[context compacted]%s\n " , colorDim , colorReset )
249250 case iteragent .EventError :
250- fmt .Printf ("\r \033 [K%sError: %s%s\n " , colorRed , e .Content , colorReset )
251+ msg := e .Content
252+ hint := authErrorHint (msg )
253+ fmt .Printf ("\r \033 [K%sError: %s%s\n " , colorRed , msg , colorReset )
254+ if hint != "" {
255+ fmt .Printf ("%sFix: %s%s\n " , colorYellow , hint , colorReset )
256+ }
251257 }
252258 return fullContent , toolStart
253259}
@@ -298,3 +304,41 @@ func printFinalStats(elapsed, ttft time.Duration, beforeTokens int, requestCostU
298304
299305 slog .Debug ("request completed" , "elapsed_ms" , elapsed .Milliseconds (), "ttft_ms" , ttft .Milliseconds (), "response_chars" , len (fullContent ), "total_tokens" , sess .Tokens , "cost_usd" , requestCostUSD )
300306}
307+
308+ // authErrorHint returns a human-readable fix suggestion for known API error
309+ // patterns, or an empty string when the error doesn't look auth-related.
310+ func authErrorHint (errMsg string ) string {
311+ lower := strings .ToLower (errMsg )
312+ switch {
313+ case strings .Contains (lower , "401" ) || strings .Contains (lower , "unauthorized" ) ||
314+ strings .Contains (lower , "invalid_api_key" ) || strings .Contains (lower , "authentication_error" ) ||
315+ strings .Contains (lower , "invalid api key" ):
316+ provider := os .Getenv ("ITERATE_PROVIDER" )
317+ switch strings .ToLower (provider ) {
318+ case "anthropic" , "" :
319+ return "Set ANTHROPIC_API_KEY in your shell: export ANTHROPIC_API_KEY=sk-ant-..."
320+ case "openai" :
321+ return "Set OPENAI_API_KEY in your shell: export OPENAI_API_KEY=sk-..."
322+ case "gemini" :
323+ return "Set GEMINI_API_KEY in your shell: export GEMINI_API_KEY=AIza..."
324+ case "groq" :
325+ return "Set GROQ_API_KEY in your shell: export GROQ_API_KEY=gsk_..."
326+ case "ollama" :
327+ return "Ollama should not require an API key — check that Ollama is running (ollama serve)"
328+ case "azure" :
329+ return "Set AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT in your shell"
330+ default :
331+ return "Check that your API key environment variable is set correctly (see /providers)"
332+ }
333+ case strings .Contains (lower , "403" ) || strings .Contains (lower , "forbidden" ) ||
334+ strings .Contains (lower , "permission_denied" ):
335+ return "Your API key may lack permissions for this model — check your billing/plan"
336+ case strings .Contains (lower , "429" ) || strings .Contains (lower , "rate_limit" ) ||
337+ strings .Contains (lower , "too many requests" ):
338+ return "Rate limit hit — wait a moment and try again, or use /model to switch to a different model"
339+ case strings .Contains (lower , "no api key" ) || strings .Contains (lower , "api key not set" ) ||
340+ strings .Contains (lower , "missing api key" ):
341+ return "Run /providers to see which environment variable needs to be set"
342+ }
343+ return ""
344+ }
0 commit comments