Skip to content

fix #95474: [Bug]: missing_tool_result (local tool-execution failure) is classified unclassified and triggers cross-provider model fallback#95543

Merged
altaywtf merged 1 commit into
openclaw:mainfrom
mikasa0818:feat/issue-95474
Jun 27, 2026
Merged

Conversation

@mikasa0818

@mikasa0818 mikasa0818 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #95474.

  • Treat Codex missing_tool_result / "native Codex tool.call without a matching tool.result" failures as local non-provider runtime/tool-execution failures.
  • Reuse the existing non-provider abort path in model failover so these errors are surfaced on the current model instead of consuming configured fallback providers.
  • Preserve provider-failure precedence: explicit provider metadata such as HTTP 503/429 still remains failover-eligible even if a nested local sentinel is present.

Maintainer checklist

  • Fix classification: Root-cause bug fix in provider/model failover classification.
  • Maintainer-ready confidence: High; the source path, sentinel string, failover gate, and regression tests line up directly with the reported missing_tool_result provider-jump behavior.
  • Root cause: Codex synthetic missing_tool_result errors were local tool-execution failures, but the model failover error boundary did not recognize that source invariant, so unknown-error fallback advanced to another provider.
  • Why this is root-cause fix: The change teaches the source-of-truth non-provider runtime gate to identify the stable Codex missing-tool-result sentinel before the generic unknown-error failover branch can consume fallback candidates; it does not merely hide the final error text.
  • Patch quality notes: The added guard/default returns are intentional negative checks: no sentinel means the existing behavior is unchanged, while explicit provider metadata remains authoritative over nested local context. This addresses the patch-quality warning without adding a broad catch-all.
  • Exact steps or command run after this patch: CI=1 node scripts/run-vitest.mjs --reporter=basic src/agents/model-fallback.test.ts src/agents/failover-error.test.ts src/agents/embedded-agent-runner/run/failover-policy.test.ts src/agents/embedded-agent-runner/result-fallback-classifier.test.ts; CI=1 node scripts/run-vitest.mjs --reporter=basic extensions/codex/src/app-server/event-projector.test.ts extensions/codex/src/app-server/run-attempt.test.ts; git diff --check HEAD~1..HEAD; PROOF_HEAD=04469ace26ffdbdf8122a6b975b23251551addba TASK_WORKTREE=/e/worktree/openclaw-worktrees/issue-95474 pnpm exec tsx /e/worktree/openclaw-issue-95474-evidence/runtime-missing-tool-result-proof.mjs.
  • Observed result after fix: All commands exited 0; the new model-fallback regression rejects the original missing-tool-result error and proves only one provider/model attempt is made. The runtime-boundary proof imported the real runWithModelFallback implementation, configured openai/gpt-5.4 plus fallback anthropic/claude-opus-4-8, and observed only openai/gpt-5.4, originalErrorRethrown: true, and fallbackAttempted: false.
  • What was not tested: I did not run a live 15-minute hanging native shell command against a real Codex OAuth session; the behavior is locked with source-level failover-boundary tests, Codex app-server sentinel coverage, and a runtime-boundary proof against the real fallback loop.
  • Why it matters / User impact: A local hung bash or missing native tool result should not silently switch a user's active session from the requested Codex model to a different provider, because the alternate provider cannot repair the local tool failure.
  • What did NOT change: No per-tool timeout policy, Codex synthetic-result creation, provider auth/billing/rate-limit/overload/server/transport classification, public API, schema, protocol payload, migration, default config, or unrelated fallback candidate ordering changed.
  • Architecture / source-of-truth check: The canonical source-of-truth is isNonProviderRuntimeCoordinationError, the existing model failover boundary for local runtime conditions; this PR extends that boundary instead of adding downstream string checks in individual provider loops.
  • Target test file: src/agents/failover-error.test.ts and src/agents/model-fallback.test.ts, plus existing guard coverage in src/agents/embedded-agent-runner/run/failover-policy.test.ts, src/agents/embedded-agent-runner/result-fallback-classifier.test.ts, extensions/codex/src/app-server/event-projector.test.ts, and extensions/codex/src/app-server/run-attempt.test.ts.
  • Scenario locked in: Direct and nested Codex missing_tool_result errors are non-provider/local; runWithModelFallback does not make a second provider attempt; explicit provider HTTP metadata remains failover-eligible.
  • Why this is the smallest reliable guardrail: A unit regression at the failover boundary proves the exact candidate-consumption invariant without needing a slow live hanging-shell fixture, while Codex tests cover generation of the sentinel this boundary consumes.
  • Risk labels considered: auth-provider, session-state, message-delivery, availability, compatibility.
  • Risk explanation: Merge risk is limited to the provider/model failover boundary. The compatibility/default behavior for public contracts is unchanged because no config, schema, protocol, SDK, wire format, migration, or default value is modified; the related open PR scan found other issue-linked PRs (fix(agents): prevent missing_tool_result from triggering cross-provider model fallback (#95474) #95494, fix(agents): classify missing_tool_result as tool_error to prevent cross-provider failover #95488, fix(agents): prevent Codex missing_tool_result from triggering cross-provider model fallback #95520), but this diff remains scoped to the same canonical non-provider classification invariant.
  • Why acceptable: The detector is intentionally narrow to missing_tool_result and the stable Codex missing native tool-result sentence, and tests prove provider metadata still wins, so real provider failures continue to rotate/fail over as before.

What Problem This Solves

When Codex synthesizes missing_tool_result for a hung/missing local tool result, the failure is caused by local tool execution, not by the selected model provider. Before this change, the failover loop treated the resulting thrown error as an unknown candidate failure while fallback candidates remained, so a local shell/tool problem could silently switch a session from the requested model to the next configured provider.

This PR keeps that failure on the current provider/model: the user sees the local tool failure instead of OpenClaw spending cross-provider fallback candidates that cannot fix the missing local tool result.

Root Cause

Codex can synthesize a failed tool result with reason: "missing_tool_result" when a native tool call (for example bash) never returns a matching tool.result. That is a local tool-execution failure: switching from openai/gpt-5.4 to a fallback provider cannot make the missing local shell result appear.

The existing model failover guard already aborts failover for non-provider runtime coordination failures such as session write-lock contention and embedded session takeover. However, the missing_tool_result sentinel was not recognized by that guard, so runWithModelFallback reached the generic unknown-error branch and advanced to the next configured candidate.

What Changed

  • Added a narrow detector for the stable Codex missing-tool-result sentinel:
    • missing_tool_result
    • native Codex tool.call without a matching tool.result
  • Included that detector in the existing non-provider runtime error gate used by runWithModelFallback.
  • Added regression coverage for:
    • direct and nested missing-tool-result errors being non-provider/local;
    • missing-tool-result errors not triggering a second fallback model attempt;
    • explicit provider failover metadata remaining authoritative.

Real behavior proof

  • Behavior or issue addressed: A Codex missing_tool_result local tool-execution failure no longer consumes model failover candidates or jumps to a different provider; it is rethrown on the current provider/model path.
  • Real environment tested: Local OpenClaw repository worktree on Windows Git Bash with Node v22.17.0 and pnpm 11.2.2. In addition to unit regression coverage, I ran a runtime-boundary proof that imports the real src/agents/model-fallback.ts from this worktree and configures a primary plus fallback provider.
  • Evidence after fix: The runtime proof configured primary openai/gpt-5.4 and fallback anthropic/claude-opus-4-8, then threw the real Codex missing-tool-result-style error from the callback passed to runWithModelFallback. The observed JSON was:
{
  "proof": "missing_tool_result runtime failover boundary",
  "head": "04469ace26ffdbdf8122a6b975b23251551addba",
  "primary": "openai/gpt-5.4",
  "configuredFallback": "anthropic/claude-opus-4-8",
  "attempts": ["openai/gpt-5.4"],
  "originalErrorRethrown": true,
  "fallbackAttempted": false
}

This exits 0 and shows the local error stops after one candidate, rethrows the original error, and does not switch to the configured fallback provider. The focused failover regression tests and Codex app-server tests also exit 0 and cover the classification and sentinel production paths.

Review findings addressed

  • RF-001 / RF-002 / RF-003: Fixed with the runtime-boundary log proof above. It imports the real src/agents/model-fallback.ts, configures primary openai/gpt-5.4 plus fallback anthropic/claude-opus-4-8, throws the Codex missing-tool-result-style local error through runWithModelFallback, and exits 0 with attempts: ["openai/gpt-5.4"], originalErrorRethrown: true, and fallbackAttempted: false. This is redacted terminal/log-style output for the after-fix fallback boundary; I still did not run a live long-running Codex OAuth shell hang because that live environment is unavailable in this local PR repair context.
  • RF-004: Disclosed for maintainer decision. The routing change is intentional for this sentinel: missing_tool_result is local tool execution, so surfacing the current-model local error is the expected behavior instead of consuming configured fallback providers that cannot repair the missing local tool result.
  • RF-005: Addressed with the narrow detector and provider-precedence regression. Matching is limited to missing_tool_result and the stable native Codex missing tool-result sentence, and the tests keep explicit provider metadata such as HTTP 503 failover-eligible even when nested context contains the local sentinel.
  • RF-006: Disclosed for maintainer decision. The related open PR scan found fix(agents): prevent missing_tool_result from triggering cross-provider model fallback (#95474) #95494, fix(agents): classify missing_tool_result as tool_error to prevent cross-provider failover #95488, and fix(agents): prevent Codex missing_tool_result from triggering cross-provider model fallback #95520; this PR remains a focused source-of-truth failover-boundary fix with proof added, so maintainers can compare it against competing fixes without extra scope in this branch.

Verification

Run from the repository worktree after formatting and committing:

CI=1 node scripts/run-vitest.mjs --reporter=basic src/agents/model-fallback.test.ts src/agents/failover-error.test.ts src/agents/embedded-agent-runner/run/failover-policy.test.ts src/agents/embedded-agent-runner/result-fallback-classifier.test.ts
# exit 0

CI=1 node scripts/run-vitest.mjs --reporter=basic extensions/codex/src/app-server/event-projector.test.ts extensions/codex/src/app-server/run-attempt.test.ts
# exit 0

git diff --check HEAD~1..HEAD
# exit 0; no whitespace errors

PROOF_HEAD=04469ace26ffdbdf8122a6b975b23251551addba TASK_WORKTREE=/e/worktree/openclaw-worktrees/issue-95474 pnpm exec tsx /e/worktree/openclaw-issue-95474-evidence/runtime-missing-tool-result-proof.mjs
# exit 0; attempts=["openai/gpt-5.4"], originalErrorRethrown=true, fallbackAttempted=false

Regression Test Plan

  • Target tests: src/agents/failover-error.test.ts and src/agents/model-fallback.test.ts cover the new invariant; src/agents/embedded-agent-runner/run/failover-policy.test.ts, src/agents/embedded-agent-runner/result-fallback-classifier.test.ts, extensions/codex/src/app-server/event-projector.test.ts, and extensions/codex/src/app-server/run-attempt.test.ts guard adjacent policy and Codex behavior.
  • Regression scenario: Direct string/Error and nested { result: { reason: "missing_tool_result" } } shapes are classified as local/non-provider, and a configured fallback chain does not make a second provider attempt for that local failure.
  • Guardrail rationale: This is the smallest reliable guardrail because it proves the exact source-of-truth failover boundary that caused provider jumping, while existing Codex tests cover sentinel production.

Merge risk

Not Changed

  • No changes to Codex's synthetic missing_tool_result creation path.
  • No per-tool timeout policy changes; this PR only prevents local tool failures from changing provider/model.
  • No changes to failover behavior for real provider/auth/billing/rate-limit/overload/server/transport failures.

Risk

Low/medium. The detection is intentionally narrow to Codex's stable missing-tool-result sentinel and only affects the already-existing non-provider abort path. The main regression risk would be accidentally suppressing a provider failure that happens to wrap a local sentinel, so the tests keep explicit provider metadata authoritative.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Jun 21, 2026
@clawsweeper

clawsweeper Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 27, 2026, 11:46 AM ET / 15:46 UTC.

Summary
This PR extends the agent non-provider runtime guard to recognize Codex missing_tool_result failures and adds regression tests for no fallback plus provider-metadata precedence.

PR surface: Source +52, Tests +89. Total +141 across 3 files.

Reproducibility: yes. at source level: current main synthesizes the Codex missing_tool_result sentinel and the fallback loop advances unrecognized thrown errors while candidates remain. I did not run a live long-hanging Codex shell turn in this read-only review.

Review metrics: 1 noteworthy metric.

  • Failover decision surface: 1 guard broadened, 0 config/schema/default surfaces changed. The diff is small, but it sits on provider/model fallback routing where compatibility and auth-provider behavior are merge-sensitive.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #95474
Summary: This PR is the remaining open proof-positive candidate fix for the canonical missing_tool_result local tool failure consuming model fallback candidates.

Members:

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

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P2] Merging intentionally changes configured fallback-chain behavior for this sentinel: a Codex local missing tool result will surface on the current model instead of advancing to later configured providers.
  • [P1] A bad marker match in provider/model routing could suppress a real provider failure, so maintainer review should explicitly accept the missing_tool_result-as-local invariant and the provider-metadata precedence tests.

Maintainer options:

  1. Accept This Narrow Classifier (recommended)
    If maintainers agree that Codex missing_tool_result is always local tool execution, land this PR as the canonical fix for the linked issue.
  2. Require One More Provider-Precedence Regression
    Before merge, ask for an additional regression with an explicit upstream/server provider payload whose diagnostic text also mentions the missing-tool marker.
  3. Pause For Native-Tool Lifecycle Direction
    If maintainers want the native tool lifecycle to eliminate this sentinel before classification, pause this PR and make that broader Codex path canonical.

Next step before merge

  • [P2] Maintainer review should accept or reject this fallback-routing behavior and choose this PR as the canonical fix; there is no narrow automated repair left.

Security
Cleared: The diff changes TypeScript failover classification and tests only; it does not add dependencies, permissions, secrets handling, downloads, or execution hooks.

Review details

Best possible solution:

Land this narrow failover-boundary classifier if maintainers accept it as the canonical fix, then close the linked issue and leave broader native-tool timeout work as separate follow-up if needed.

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

Yes at source level: current main synthesizes the Codex missing_tool_result sentinel and the fallback loop advances unrecognized thrown errors while candidates remain. I did not run a live long-hanging Codex shell turn in this read-only review.

Is this the best way to solve the issue?

Yes, pending maintainer acceptance: extending the existing non-provider runtime guard is the narrowest owner-boundary fix because that guard already runs before fallback candidates are consumed. Adding a new public failover reason or patching downstream loops would be broader and more compatibility-sensitive.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against ddedf13190c1.

Label changes

Label justifications:

  • P2: The PR targets a normal-priority provider/model fallback bug with visible session impact but bounded blast radius.
  • merge-risk: 🚨 compatibility: Existing users with configured fallback chains will see this Codex sentinel stop on the current model instead of advancing to later fallback providers.
  • merge-risk: 🚨 auth-provider: The affected code controls provider/model fallback routing and configured model-chain selection.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes after-fix runtime-boundary output showing the real fallback loop attempts only openai/gpt-5.4 and does not switch to the configured fallback provider.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix runtime-boundary output showing the real fallback loop attempts only openai/gpt-5.4 and does not switch to the configured fallback provider.
Evidence reviewed

PR surface:

Source +52, Tests +89. Total +141 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 1 58 6 +52
Tests 2 89 0 +89
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 147 6 +141

What I checked:

  • Repository policy read: Root AGENTS.md and src/agents/AGENTS.md were read fully; their Codex-source and provider-fallback compatibility review rules shaped this verdict. (AGENTS.md:10, ddedf13190c1)
  • Current main synthesizes the sentinel: Current main defines the stable missing-tool-result text and records unmatched native tool calls as failed tool.result events with reason missing_tool_result. (extensions/codex/src/app-server/event-projector.ts:114, ddedf13190c1)
  • Current main still falls back on unrecognized thrown errors: The existing non-provider guard only covers session write-lock and embedded takeover, while runWithModelFallback continues unrecognized errors when candidates remain. (src/agents/model-fallback.ts:1793, ddedf13190c1)
  • PR adds the narrow classifier at the shared boundary: The PR adds a missing_tool_result marker/text detector to isNonProviderRuntimeCoordinationError, preserving the existing source-of-truth abort path rather than adding a downstream loop special case. (src/agents/failover-error.ts:369, baecd4a25d9a)
  • PR locks the fallback behavior and provider precedence: The added tests prove the missing-tool-result error makes only one model attempt, while plain provider text and explicit 503 provider metadata remain fallback-eligible. (src/agents/model-fallback.test.ts:755, baecd4a25d9a)
  • Codex dependency contract checked: OpenAI Codex app-server protocol treats item/completed as authoritative final state, and tool-call item statuses include failed; OpenClaw owns the missing_tool_result sentinel derived from that local tool lifecycle. (../codex/codex-rs/app-server/README.md:1387, c464468493e9)

Likely related people:

  • vincentkoc: Recent GitHub path history includes model-fallback refactors and co-authored failover-error work around provider routing and fallback classification. (role: recent area contributor; confidence: high; commits: 80805ad7a583, aa3797c8d0d7, 7c97c6da9bfa; files: src/agents/model-fallback.ts, src/agents/failover-error.ts, extensions/codex/src/app-server/event-projector.ts)
  • steipete: Recent history shows work on the OpenClaw agent runtime, model-fallback documentation, and Codex terminal/event-projector behavior tied to this bug family. (role: adjacent feature owner; confidence: medium; commits: bb46b79d3c14, fbf3e009d49e, a09f6b1b2787; files: src/agents/model-fallback.ts, src/agents/embedded-agent-runner/run/failover-policy.ts, extensions/codex/src/app-server/event-projector.ts)
  • fuller-stack-dev: Recent merged work touched structured provider error signals, post-tool final-turn recovery, and terminal classifier behavior adjacent to the Codex missing-tool-result path. (role: adjacent terminal-classifier contributor; confidence: medium; commits: 89975eea24a5, 77a682c5def2, 0314819f918a; files: src/agents/failover-error.ts, extensions/codex/src/app-server/event-projector.ts, src/agents/embedded-agent-runner/result-fallback-classifier.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. labels Jun 21, 2026
@NianJiuZst

Copy link
Copy Markdown
Contributor

Complementary observations on top of ClawSweeper's review:

This targets #95474 (missing_tool_result) not #95519 (upstream_error). Both are "errors not classified correctly" bugs but they live in different code paths and have different classifications.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 21, 2026
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jun 22, 2026
@altaywtf altaywtf self-assigned this Jun 27, 2026
@altaywtf
altaywtf force-pushed the feat/issue-95474 branch 3 times, most recently from 6c88af6 to d6fe914 Compare June 27, 2026 11:19
@altaywtf
altaywtf merged commit b9c6414 into openclaw:main Jun 27, 2026
93 checks passed
@altaywtf

Copy link
Copy Markdown
Member

Merged via squash.

QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
wheakerd pushed a commit to wheakerd/clawdbot that referenced this pull request Jul 15, 2026
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. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: missing_tool_result (local tool-execution failure) is classified unclassified and triggers cross-provider model fallback

3 participants