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 = true

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Inspect the wrapped error (and the logged 'error' field) — it distinguishes auth (401/403), model-not-found, rate limit, and network causes
  2. Fix credentials/model id/base URL first; retries cannot cure a 4xx config error
  3. For transient causes, raise retry counts and per-call timeouts, or back off and retry the turn later
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/02f017e00e3f5aba. Report an issue: GitHub.