Skip to content

fix(agents): classify upstream_error as fallbackable to harden model fallback#96372

Closed
hugenshen wants to merge 0 commit into
openclaw:mainfrom
hugenshen:fix/fallback-upstream-error-underscore
Closed

fix(agents): classify upstream_error as fallbackable to harden model fallback#96372
hugenshen wants to merge 0 commit into
openclaw:mainfrom
hugenshen:fix/fallback-upstream-error-underscore

Conversation

@hugenshen

@hugenshen hugenshen commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes #95519. When a primary model's provider returns an error with type: "upstream_error" (the OpenAI-compatible "the upstream backend failed" shape, e.g. {"error":{"message":"Upstream request failed","type":"upstream_error","param":"","code":null}}), OpenClaw ends the turn with LLM request failed instead of trying the configured agents.defaults.model.fallbacks. The trajectory shows only the primary model, no model.fallback_step event, and no fallback attempt.

Root cause is a classification gap. The provider error surfaces as an assistant message with stopReason: "error" and errorType: "upstream_error". The embedded runner's assistant-stage failover gate (run.ts calls isFailoverAssistantError / classifyAssistantFailoverReason) is what decides whether to throw a FailoverError into the model-fallback loop. classifyAssistantFailoverReason runs classifyFailoverSignal, and nothing mapped upstream_error to a failover reason:

  • upstream_error is lowercase snake_case, so it was not recognized as a transient provider type, and
  • the message text Upstream request failed matches no transient classifier, and
  • there is no recognized HTTP status in this payload to fall back on.

So the gate returned null ("not a failover error"), the assistant stage took the continue_normal path instead of throwing, and the run returned a terminal error payload (non_deliverable_terminal_turn) that the model-fallback loop treats as a successful result — the fallback chain never runs.

Why This Change Was Made

The fix classifies upstream_error as a transient, fallbackable reason at the point the signal is built, so the existing assistant-stage failover machinery throws a FailoverError and the model-fallback loop advances to the next configured model — the same path already used for Codex server_error (run.codex-server-error-fallback.test.ts).

Three changes in src/agents/embedded-agent-helpers/errors.ts and src/agents/failover-error.ts:

  1. New classifyFailoverReasonFromErrorType in errors.ts, run early in classifyFailoverSignal (before message/status classification): upstream_error → timeout, server_error → server_error; unknown types return null and fall through. This is the change that fixes [Bug]: Fallback should trigger on provider upstream_error / LLM request failed #95519, because buildAssistantFailoverSignal passes the assistant message's errorType straight into classifyFailoverSignal, so the gate now recognizes upstream_error.
  2. isStableProviderErrorType in failover-error.ts now also accepts lowercase_snake_case (in addition to SCREAMING_SNAKE_CASE). This only affects readDirectErrorType, used by the thrown-error coercion path (resolveFailoverReasonFromError), so a thrown upstream_error payload is also extractable. The generic-type exclusion list (api_error, server_error, etc.) is unchanged.
  3. API_ERROR_TRANSIENT_SIGNALS_RE matches upstream.error, so both upstream error and upstream_error are caught in the message-text path used for api_error-typed payloads (defense-in-depth).

Non-transient types (auth, context overflow, validation) are unaffected and continue to return null.

User Impact

With agents.defaults.model.fallbacks configured, a primary-model upstream_error is now treated as a fallbackable failure: the run throws a FailoverError, the model-fallback loop skips the failed candidate and tries the next model, and the trajectory records a fallback step. If all candidates fail, the user gets a FallbackSummaryError with per-attempt detail instead of the generic LLM request failed. No behavior change when no fallback is configured, and no change for non-transient errors.

Evidence

Focused regression coverage in src/agents/failover-error.test.ts, including a test that drives the exact production gate the runtime uses (classifyAssistantFailoverReason / isFailoverAssistantError) with the #95519 assistant-message shape (stopReason: "error", errorType: "upstream_error", message Upstream request failed, no HTTP status). This message returns null from the gate without the fix (verified: message-only classifyFailoverSignal is null) and timeout with it, so it is a true regression test of the runtime path — not just an isolated signal-classifier call.

Command:

pnpm test src/agents/failover-error.test.ts

Result:

 Test Files  1 passed (1)
      Tests  97 passed (97)
   Duration  565ms

New/relevant cases:

✓ upstream_error type triggers fallback > classifies upstream_error errorType as timeout (fallbackable)
✓ upstream_error type triggers fallback > classifies server_error errorType as server_error (fallbackable)
✓ upstream_error type triggers fallback > resolves upstream_error via resolveFailoverReasonFromError with nested payload
✓ upstream_error type triggers fallback > does not classify unknown errorType as fallbackable
✓ upstream_error type triggers fallback > classifies upstream_error via JSON api_error transient signals (regex)
✓ upstream_error type triggers fallback > treats an upstream_error assistant message as fallbackable at the production gate

No-regression check on the adjacent failover surfaces the change touches:

pnpm test src/agents/embedded-agent-runner/result-fallback-classifier.test.ts src/agents/embedded-agent-helpers/failover-matches.test.ts
 Test Files  2 passed (2)
      Tests  47 passed (47)

The transport layer already extracts a nested provider type into errorType (proven for the identical code path by the rate_limit_error case in src/agents/openai-transport-stream.test.ts), which is how errorType: "upstream_error" reaches the gate above.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. size: S labels Jun 24, 2026
@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded: the same upstream_error model-fallback bug is already fixed on current main by the merged canonical PR, while this PR is now conflicting and only leaves an unproven defense-in-depth thrown-error variant.

Root-cause cluster
Relationship: superseded
Canonical: #95542
Summary: This PR targets the same upstream_error fallback bug as the merged canonical PR, which now fixes the central current-main behavior and closed the linked issue.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Canonical path: GitHub reports #95542 as merged, with merge commit cfd8ba8 fixing the same linked bug and touching the same classifier boundaries.

So I’m closing this here and keeping the remaining discussion on #95542.

Review details

Best possible solution:

Close this PR as superseded and keep the merged current-main implementation as the canonical fix; handle any remaining direct thrown-error coercion case in a new narrow issue or PR with proof.

Do we have a high-confidence way to reproduce the issue?

Yes: the linked bug is source-reproducible through assistant and embedded result fallback classifiers, and current main now has focused tests for the reported upstream_error payload shape. I did not run a live provider outage reproduction in this read-only review.

Is this the best way to solve the issue?

No: this PR is no longer the best landing path because the merged canonical PR covers the same central assistant path plus embedded result-payload path on current main. The remaining thrown-error coercion tweak is a separate unproven case that should not keep this superseded branch open.

Security review:

Security review cleared: No concrete security or supply-chain concern was found; the diff changes internal agent fallback classification and tests only.

AGENTS.md: found and applied where relevant.

What I checked:

Likely related people:

  • mikasa0818: Authored the merged canonical fix for the same upstream_error fallback bug and the embedded result payload test coverage. (role: canonical fix author; confidence: high; commits: e67b78af640f, cfd8ba8fc81d; files: src/agents/embedded-agent-helpers/errors.ts, src/agents/embedded-agent-runner/result-fallback-classifier.ts)
  • altaywtf: Authored the final classifier refactor commit on the canonical PR and posted the squash-merge provenance for the landed commit. (role: canonical fix merger and refactor author; confidence: high; commits: f5a70ef7bb10, cfd8ba8fc81d; files: src/agents/embedded-agent-helpers/errors.ts, src/agents/embedded-agent-runner/result-fallback-classifier.ts)
  • steipete: Recent history shows related work on terminal classifiers and model fallback transition behavior near this provider-fallback boundary. (role: recent fallback-boundary contributor; confidence: medium; commits: 0314819f918a, 4b0f16d496e5; files: src/agents/embedded-agent-runner/result-fallback-classifier.ts, docs/concepts/model-failover.md)

Codex review notes: model internal, reasoning high; reviewed against 785ab7477982; fix evidence: commit cfd8ba8fc81d, main fix timestamp 2026-06-29T14:12:09+03:00.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jun 24, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 24, 2026
@hugenshen

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

@hugenshen thanks for the PR. ClawSweeper is still waiting on real behavior proof before this can move forward.

Useful proof can be a screenshot, short video, terminal output, copied live output, linked artifact, or redacted logs that show the changed behavior after the fix. Please redact private tokens, phone numbers, private endpoints, customer data, and anything else sensitive.

Once proof is added to the PR body or a comment, ClawSweeper or a maintainer can re-check it.

@hugenshen
hugenshen force-pushed the fix/fallback-upstream-error-underscore branch from 939e1b6 to ebe9937 Compare July 7, 2026 16:11
@hugenshen hugenshen closed this Jul 7, 2026
@hugenshen
hugenshen force-pushed the fix/fallback-upstream-error-underscore branch from ebe9937 to 453f596 Compare July 7, 2026 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Fallback should trigger on provider upstream_error / LLM request failed

1 participant