fix(codex): classify get_goal read statuses as successful dynamic tool calls#98659
Conversation
…l calls isCodexToolResultError fail-closes any dynamic-tool result whose details.status is absent from its non-error allowlist. get_goal returns details.status "found" or "missing" (a successful read of the thread goal, or its absence), neither of which was in the allowlist, so every get_goal call was classified as an error: reported to codex as success: false and persisted on the transcript with isError: true. Sibling openclaw#96856 added the write-side goal statuses (created/updated) and the accepted spawn status but missed the read-side get_goal statuses. This adds found/missing alongside them. Genuinely failed statuses stay fail-closed.
|
Codex review: needs maintainer review before merge. Reviewed July 1, 2026, 10:38 AM ET / 14:38 UTC. Summary PR surface: Source +2, Tests +47. Total +49 across 2 files. Reproducibility: yes. Source inspection shows current main Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Next step before merge
Security Review detailsBest possible solution: Land the narrow Codex bridge classifier fix with focused regression coverage if the current head checks remain green. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main Is this the best way to solve the issue? Yes. Updating the Codex dynamic-tool classifier is the narrow origin fix because that function directly drives the response AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ce4a259485f2. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +2, Tests +47. Total +49 across 2 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
|
…l calls (openclaw#98659) isCodexToolResultError fail-closes any dynamic-tool result whose details.status is absent from its non-error allowlist. get_goal returns details.status "found" or "missing" (a successful read of the thread goal, or its absence), neither of which was in the allowlist, so every get_goal call was classified as an error: reported to codex as success: false and persisted on the transcript with isError: true. Sibling openclaw#96856 added the write-side goal statuses (created/updated) and the accepted spawn status but missed the read-side get_goal statuses. This adds found/missing alongside them. Genuinely failed statuses stay fail-closed.
…l calls (openclaw#98659) isCodexToolResultError fail-closes any dynamic-tool result whose details.status is absent from its non-error allowlist. get_goal returns details.status "found" or "missing" (a successful read of the thread goal, or its absence), neither of which was in the allowlist, so every get_goal call was classified as an error: reported to codex as success: false and persisted on the transcript with isError: true. Sibling openclaw#96856 added the write-side goal statuses (created/updated) and the accepted spawn status but missed the read-side get_goal statuses. This adds found/missing alongside them. Genuinely failed statuses stay fail-closed.
…l calls (openclaw#98659) isCodexToolResultError fail-closes any dynamic-tool result whose details.status is absent from its non-error allowlist. get_goal returns details.status "found" or "missing" (a successful read of the thread goal, or its absence), neither of which was in the allowlist, so every get_goal call was classified as an error: reported to codex as success: false and persisted on the transcript with isError: true. Sibling openclaw#96856 added the write-side goal statuses (created/updated) and the accepted spawn status but missed the read-side get_goal statuses. This adds found/missing alongside them. Genuinely failed statuses stay fail-closed.
What Problem This Solves
Fixes an issue where users running the Codex harness with thread goals enabled would see every
get_goalcall reported to the model as a failed tool call, even though the read succeeded. On every call the tool ran and returned the current goal (or reported that no goal was set), yet the model was told the read failed. The failure was also persisted on the transcript withisError: trueand drove the diagnostic terminal type toerror, so it polluted session history, diagnostics, and any error-gated heuristics.Why This Change Was Made
Root cause:
isCodexToolResultError(extensions/codex/src/app-server/dynamic-tools.ts:1159) fail-closes. Any dynamic-tool result whosedetails.statusis not in its non-error allowlist is classified as an error.get_goalreturnsjsonResult(snapshot)(src/agents/tools/goal-tools.ts:87), andgetSessionGoalsetsdetails.statusto"found"(a goal exists) or"missing"(no goal) (src/config/sessions/goals.ts:170,:189). Neither value is in the allowlist, so the classifier returnstrue, the bridge sendssuccess: falseto Codex, and the result is persisted withisError: true.The fix adds the two read-side statuses to the same non-error allowlist, next to the write-side statuses their sibling added:
found/missingare the only two statusesget_goalcan emit, and both represent a successful read. Genuinely failed statuses (error,forbidden, and the rest) remain fail-closed.This is the right boundary:
isCodexToolResultErroris the single owner of the Codex dynamic-tool error/success classification, and this matches the boundary that merged sibling #96856 (b3ff64145e) already chose for the write-side goal statuses. That PR addedcreated/updated(goal writes) andaccepted(spawn) but missed the read-sideget_goalstatuses, so currentmainstill carries the defect. The Codex contract confirmssuccessis load-bearing: OpenClaw'sDynamicToolResponse.successis consumed by Codex atcodex-rs/core/src/tools/handlers/dynamic.rs:124and mapped intoFunctionToolOutput::from_content(body, Some(success)), which becomesFunctionCallOutputPayload.success(codex-rs/protocol/src/models.rs:1384) and theDynamicToolCallResponseevent, sosuccess: falsemarks the tool call failed to the model.User Impact
Codex-harness users with goals enabled now get an accurate signal: reading the thread goal is reported to the model as a successful tool call instead of a failure on every call. This stops false failures from polluting session history and diagnostics and from feeding error-gated heuristics. Genuinely failed goal reads are unaffected.
Evidence
Real runtime, before and after. Drove the real Codex dynamic-tool bridge (
createCodexDynamicToolBridge().handleToolCall) with the realget_goaltool (createGetGoalTool) against a real on-disk session store, on pristine main f936c6b vs the patched tree, identical inputs. The classifier, the goal tool, the session store read/write, and the persisted-result callback all stayed real; nothing external was mocked.Steps: seed a temp
sessions.json, invoke the bridge once with no goal present, create a real goal viacreateSessionGoal, then invoke the bridge again; printdetails.status, the responsesuccess, and the persistedisErrorfor each call.Both a present goal and an absent goal now return the read as a successful tool call to the harness instead of a failure, and the persisted result flag flips from failed to ok. Genuinely failed statuses are unchanged.
Checks:
node scripts/run-vitest.mjs extensions/codex/src/app-server/dynamic-tools.test.ts: 81 passed with the patch; the newget_goalcase fails on pristine main and passes with the fix.node scripts/run-oxlint.mjsandoxfmt --checkon both changed files: clean.node scripts/run-tsgo.mjs -p tsconfig.core.jsonand-p test/tsconfig/tsconfig.core.test.json: clean.Not covered: no live Codex subprocess round-trip or live OpenAI provider request; the Codex-side mapping of
successto the failed item status is established from the pinnedcodex-rscontract rather than a live run. Full build not run.Real behavior proof
Behavior addressed: under the Codex harness with goals enabled, every successful get_goal read is reported to the model as a failed tool call (success false, persisted isError true).
Real environment tested: the real Codex dynamic-tool bridge (createCodexDynamicToolBridge().handleToolCall) driving the real get_goal tool (createGetGoalTool) against a real on-disk session store, on pristine main f936c6b and on the patched tree; the classifier, goal tool, session store, and persisted-result callback stayed real, nothing external mocked.
Exact steps or command run after this patch: seed a temp sessions.json, invoke the bridge with no goal present, create a real goal via createSessionGoal, invoke the bridge again, print details.status plus response success plus persisted isError.
Evidence after fix:
Observed result after fix: both a present goal and an absent goal now return the read as a successful tool call to the harness instead of a failure, with the persisted result flag flipped from failed to ok; genuinely failed statuses are unchanged.
What was not tested: no live Codex subprocess round-trip or live provider request; the Codex-side mapping of success to the failed item status comes from the pinned codex-rs contract rather than a live run; full build not run.