From adcfebc276ccf2f1f4ddab9ef9a8e35f5011c317 Mon Sep 17 00:00:00 2001 From: Yuval Dinodia <102706514+yetval@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:36:38 -0400 Subject: [PATCH] fix(codex): classify get_goal read statuses as successful dynamic tool calls (#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 #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. --- .../src/app-server/dynamic-tools.test.ts | 47 +++++++++++++++++++ .../codex/src/app-server/dynamic-tools.ts | 2 + 2 files changed, 49 insertions(+) diff --git a/extensions/codex/src/app-server/dynamic-tools.test.ts b/extensions/codex/src/app-server/dynamic-tools.test.ts index d3cc0b32143a..9954c8633a14 100644 --- a/extensions/codex/src/app-server/dynamic-tools.test.ts +++ b/extensions/codex/src/app-server/dynamic-tools.test.ts @@ -402,6 +402,53 @@ describe("createCodexDynamicToolBridge", () => { expect(updatedResult.success).toBe(true); }); + it("treats get_goal read statuses (found / missing) as successful dynamic tool calls", async () => { + const onFoundResult = vi.fn(); + const foundBridge = createBridgeWithToolResult( + "get_goal", + textToolResult('{\n "status": "found"\n}', { + status: "found", + goal: { objective: "ship the fix", status: "active" }, + }), + ); + const foundResult = await foundBridge.handleToolCall( + { + threadId: "thread-1", + turnId: "turn-1", + callId: "call-found", + namespace: null, + tool: "get_goal", + arguments: {}, + }, + { onAgentToolResult: onFoundResult }, + ); + expect(foundResult.success).toBe(true); + expect(onFoundResult).toHaveBeenCalledWith( + expect.objectContaining({ toolName: "get_goal", isError: false }), + ); + + const onMissingResult = vi.fn(); + const missingBridge = createBridgeWithToolResult( + "get_goal", + textToolResult('{\n "status": "missing"\n}', { status: "missing" }), + ); + const missingResult = await missingBridge.handleToolCall( + { + threadId: "thread-1", + turnId: "turn-1", + callId: "call-missing", + namespace: null, + tool: "get_goal", + arguments: {}, + }, + { onAgentToolResult: onMissingResult }, + ); + expect(missingResult.success).toBe(true); + expect(onMissingResult).toHaveBeenCalledWith( + expect.objectContaining({ toolName: "get_goal", isError: false }), + ); + }); + it("keeps available and registered schemas paired with their tools", () => { const bridge = createCodexDynamicToolBridge({ tools: [ diff --git a/extensions/codex/src/app-server/dynamic-tools.ts b/extensions/codex/src/app-server/dynamic-tools.ts index 2eff0023d1dd..605aaadb8adc 100644 --- a/extensions/codex/src/app-server/dynamic-tools.ts +++ b/extensions/codex/src/app-server/dynamic-tools.ts @@ -1187,6 +1187,8 @@ function isCodexToolResultError(result: AgentToolResult): boolean { status !== "created" && status !== "updated" && status !== "accepted" && + status !== "found" && + status !== "missing" && status !== "pending" && status !== "started" && status !== "running" &&