{"record":{"id":"02f017e00e3f5aba","repo":"sipeed/picoclaw","slug":"llm-call-failed-after-retries-w","errorCode":null,"errorMessage":"LLM call failed after retries: %w","messagePattern":"LLM call failed after retries: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/agent/pipeline_llm.go","lineNumber":470,"sourceCode":"\t}\n\n\tif err != nil {\n\t\tal.emitEvent(\n\t\t\truntimeevents.KindAgentError,\n\t\t\tts.eventMeta(\"runTurn\", \"turn.error\"),\n\t\t\tErrorPayload{\n\t\t\t\tStage:   \"llm\",\n\t\t\t\tMessage: err.Error(),\n\t\t\t},\n\t\t)\n\t\tlogger.ErrorCF(\"agent\", \"LLM call failed\",\n\t\t\tmap[string]any{\n\t\t\t\t\"agent_id\":  ts.agent.ID,\n\t\t\t\t\"iteration\": iteration,\n\t\t\t\t\"model\":     exec.llmModel,\n\t\t\t\t\"error\":     err.Error(),\n\t\t\t})\n\t\treturn ControlBreak, fmt.Errorf(\"LLM call failed after retries: %w\", err)\n\t}\n\n\t// AfterLLM hook\n\tif p.Hooks != nil {\n\t\tllmResp, decision := p.Hooks.AfterLLM(turnCtx, &LLMHookResponse{\n\t\t\tMeta:     ts.eventMeta(\"runTurn\", \"turn.llm.response\"),\n\t\t\tContext:  cloneTurnContext(ts.turnCtx),\n\t\t\tModel:    exec.llmModel,\n\t\t\tResponse: exec.response,\n\t\t})\n\t\tswitch decision.normalizedAction() {\n\t\tcase HookActionContinue, HookActionModify:\n\t\t\tif llmResp != nil && llmResp.Response != nil {\n\t\t\t\texec.response = llmResp.Response\n\t\t\t}\n\t\tcase HookActionAbortTurn:\n\t\t\tcancelConfiguredStreamingLLM(turnCtx, exec)\n\t\t\texec.abortedByHook = true","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/agent/pipeline_llm.go#L452-L488","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"func llmConfigured(model, apiKey, baseURL string) error {\n    if strings.TrimSpace(model) == \"\" {\n        return errors.New(\"model is empty\")\n    }\n    if strings.TrimSpace(apiKey) == \"\" {\n        return errors.New(\"api key missing\")\n    }\n    if u, err := url.Parse(baseURL); err != nil || u.Scheme == \"\" {\n        return errors.New(\"base URL invalid\")\n    }\n    return nil\n}","typeGuard":"func isLLMRetriesExhausted(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"LLM call failed after retries\")\n}","tryCatchPattern":"result, err := pipeline.Run(ctx)\nif err != nil {\n    if isLLMRetriesExhausted(err) {\n        if classified := providers.ClassifyError(errors.Unwrap(err), \"\", \"\"); classified != nil {\n            switch classified.Reason {\n            case providers.FailoverAuth: // permanent: fix creds, do not retry\n                return fixCredentialsAndFail(err)\n            default: // transient: backoff and retry the turn later\n                time.Sleep(backoff) // scheduled retry, not a poll loop\n                return pipeline.Run(ctx)\n            }\n        }\n    }\n    return result, err\n}","preventionTips":["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"],"tags":["llm","retry","providers","network","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}