sipeed/picoclaw · error
LLM call failed after retries: %w
Error message
LLM call failed after retries: %w
What it means
The final wrapper returned by the turn's LLM stage when the call to the provider failed and every retry (classified by transientLLMRetryReason: timeout, network, rate_limit/overloaded, server_error) is exhausted. The %w chain preserves the underlying provider error, which is where the real diagnosis lives; the wrapper itself means 'retry budget spent'.
Source
Thrown at pkg/agent/pipeline_llm.go:470
}
if err != nil {
al.emitEvent(
runtimeevents.KindAgentError,
ts.eventMeta("runTurn", "turn.error"),
ErrorPayload{
Stage: "llm",
Message: err.Error(),
},
)
logger.ErrorCF("agent", "LLM call failed",
map[string]any{
"agent_id": ts.agent.ID,
"iteration": iteration,
"model": exec.llmModel,
"error": err.Error(),
})
return ControlBreak, fmt.Errorf("LLM call failed after retries: %w", err)
}
// AfterLLM hook
if p.Hooks != nil {
llmResp, decision := p.Hooks.AfterLLM(turnCtx, &LLMHookResponse{
Meta: ts.eventMeta("runTurn", "turn.llm.response"),
Context: cloneTurnContext(ts.turnCtx),
Model: exec.llmModel,
Response: exec.response,
})
switch decision.normalizedAction() {
case HookActionContinue, HookActionModify:
if llmResp != nil && llmResp.Response != nil {
exec.response = llmResp.Response
}
case HookActionAbortTurn:
cancelConfiguredStreamingLLM(turnCtx, exec)
exec.abortedByHook = trueView on GitHub (pinned to 49183d7e8d)
Solutions
- Inspect the wrapped error (and the logged 'error' field) — it distinguishes auth (401/403), model-not-found, rate limit, and network causes
- Fix credentials/model id/base URL first; retries cannot cure a 4xx config error
- For transient causes, raise retry counts and per-call timeouts, or back off and retry the turn later
- Check provider status pages / network egress if the cause is 5xx or connectivity
Defensive patterns
Strategy: retry
Validate before calling
func llmConfigured(model, apiKey, baseURL string) error {
if strings.TrimSpace(model) == "" {
return errors.New("model is empty")
}
if strings.TrimSpace(apiKey) == "" {
return errors.New("api key missing")
}
if u, err := url.Parse(baseURL); err != nil || u.Scheme == "" {
return errors.New("base URL invalid")
}
return nil
} Type guard
func isLLMRetriesExhausted(err error) bool {
return err != nil && strings.Contains(err.Error(), "LLM call failed after retries")
} Try / catch
result, err := pipeline.Run(ctx)
if err != nil {
if isLLMRetriesExhausted(err) {
if classified := providers.ClassifyError(errors.Unwrap(err), "", ""); classified != nil {
switch classified.Reason {
case providers.FailoverAuth: // permanent: fix creds, do not retry
return fixCredentialsAndFail(err)
default: // transient: backoff and retry the turn later
time.Sleep(backoff) // scheduled retry, not a poll loop
return pipeline.Run(ctx)
}
}
}
return result, err
} Prevention
- Validate API keys, model ids, and base URLs at startup, not on first call
- Classify the wrapped error before retrying — never retry 4xx auth/model errors
- Tune per-call timeouts upward for long generations instead of relying on retries
When it happens
Trigger: Persistent provider failure across all retries: invalid API key/401, unknown model/404, sustained 5xx or rate limiting, network unreachable to the provider endpoint, or context cancellation during the final attempt. Emitted with a ControlBreak and an error event on the 'llm' stage.
Common situations: Expired/missing API keys; wrong base URL or model id; provider outage or aggressive rate limits; proxies/firewalls dropping connections; timeouts too short for long generations combined with low retry counts.
Related errors
- transcription request failed: %w
- after %d retries: %w
- fallback model %q has no active provider
- failed to send request: %w
- failed to read response: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/02f017e00e3f5aba.
Report an issue: GitHub.