fix(agents): classify upstream_error provider transport failures as fallbackable#96509
fix(agents): classify upstream_error provider transport failures as fallbackable#96509googlerest wants to merge 2 commits into
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed June 24, 2026, 1:25 PM ET / 17:25 UTC. Summary PR surface: Source +10, Tests +19. Total +29 across 3 files. Reproducibility: yes. at source level: current main can be traced from the embedded error payload classifier to a null result for server_error/timeout provider payloads, which prevents runWithModelFallback from advancing. I did not run a live provider outage reproduction in this read-only review. Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Update the production embedded result fallback classifier to accept the transient provider error reasons this matcher now detects, preserve the existing visible-output and policy guards, add result-classifier coverage, and require real fallback proof. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main can be traced from the embedded error payload classifier to a null result for server_error/timeout provider payloads, which prevents runWithModelFallback from advancing. I did not run a live provider outage reproduction in this read-only review. Is this the best way to solve the issue? No: matcher coverage is useful but not the best complete fix because the production embedded result fallback gate still needs to accept the transient reason and have coverage. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 47d3d1b1f1b2. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +10, Tests +19. Total +29 across 3 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Closing this in favor of #95542. My patch only covers the matcher layer ( #95542 covers both layers (structured errorType classification + the embedded result classifier allowlist) and already has sufficient real behavior proof, including an independent live repro. No need to duplicate effort here. |
Fixes #95519
What Problem This Solves
When the primary provider returns a transport-level
upstream_error({"error":{"message":"Upstream request failed","type":"upstream_error",...}}), OpenClaw ends the turn immediately with the genericLLM request failederror instead of trying any configured fallback models underagents.defaults.model.fallbacks. The trajectory shows only the primary provider/model — nomodel.fallback_stepevents, no fallback attempt — even though this is exactly the class of transient failure the fallback chain exists for.Why This Change Was Made
Failover classification in
src/agents/embedded-agent-helpers/errors.ts/failover-matches.tsmatches known transient error shapes (server_error,overloaded, rate limits, timeouts, etc.) against the raw error text/JSON to decide whether to engage the configured fallback chain. Neither the structured JSON shape"type":"upstream_error"nor the bare messageUpstream request failedmatched any existing pattern —"upstream error"(with a space) and"upstream connect error"were already covered, but not the underscoredupstream_errortype or its associated message text, so this exact failure fell through to "unclassified" and ended the turn.Fix:
isStructuredServerErrorMessageinerrors.tsnow also matches"type":"upstream_error"(alongside the existing"type":"server_error"/"code":"server_error"checks), since it is structurally the same kind of relay/transport failure.ERROR_PATTERNS.serverErrorinfailover-matches.tsnow also matches the bare stringsupstream_errorandupstream request failed, covering callers that only see the flattened message text rather than the full JSON body.Both lead to an already-existing, already-fallbackable
FailoverReason(server_error/timeout) — no new reason code, no change to non-transient error handling.User Impact
Configured fallback models now get a chance to handle a primary-provider
upstream_errortransport failure instead of the turn ending immediately. Non-transient/intentional errors (auth, billing, format) are unaffected — the new checks only add a previously-unmatched transient shape.Evidence
Behavior addressed: neither the JSON-shaped
"type":"upstream_error"provider error nor its plain messageUpstream request failedwere classified as a fallbackableFailoverReason, soagents.defaults.model.fallbackswas never engaged.Real environment tested: macOS arm64 (M4), Darwin 25.5.0, Node v25.9.0, branch
fix/fallback-trigger-on-upstream-error, commit2cf755c2b7.Exact steps or command run after this patch:
Evidence after fix:
Observed result after fix: added 3 regression tests reproducing the exact issue shape — the full
{"error":{"type":"upstream_error",...}}JSON body now classifies as"server_error"viaclassifyFailoverReason, the bare message"Upstream request failed"classifies as"timeout", andisServerErrorMessage("upstream_error")returnstrue. Confirmed all three fail against the pre-fix code (returnnull/false) by reverting just the two source files while keeping the tests, then pass after restoring the fix.What was not tested: did not reproduce against a live provider that actually emits
upstream_error(no credentials/access to such a provider in this environment) — this is a classifier unit fix verified against the exact error shape quoted in the issue, not a live end-to-end fallback round-trip.