#2384 · Conditionally selected plugin tool is not added when a released thread resumes
Verdict: PARTIALLY REPRODUCED · Root-cause confidence: high
1. TL;DR
Codex still loses a plugin tool that appears after an idle thread stops and resumes. BB selects the tool and sends it through the server, daemon, runtime, and Codex bridge. Codex app-server version 0.150.1 does not define dynamic tools on thread/resume. It resumes the old tool catalog and reports zero matching tools. A fresh Codex thread calls the same tool successfully.
Claude Code version 2.1.247 did not show the fault on this base commit. It found and called the tool after the same stop and resume sequence. The issue is valid for Codex, but its current cross-provider claim is not valid.
2. Claims vs findings
| Claim from the issue | Status | Evidence |
|---|---|---|
| A tool selected during the first construction is callable. | Verified | A fresh Codex control returned DYNAMIC_TOOL_RESUME_OK. |
| A false-to-true selection change fails after release and resume on Codex. | Verified | The revised script returned dynamic_tool_resume_probe is unavailable. from the resumed Codex thread. |
| A false-to-true selection change fails after release and resume on Claude Code. | Refuted on base | Claude Code found the MCP tool, called it, and returned DYNAMIC_TOOL_RESUME_OK. |
| The configure selection becomes true before the failed resumed Codex turn. | Verified | The recorded runtime resume request contains dynamic_tool_resume_probe. |
| The fault occurs after plugin selection. | Verified | The server, daemon, runtime, and bridge all carried the selected tool. |
thread/resume supports dynamic tools for Codex. |
Refuted | The generated Codex 0.150.1 resume contract has no dynamicTools field. |
3. Environment
- BB commit:
ad79bbb5ec909524f8f281e62d860c588a86f332. - Issue version:
0.39.1-nightly.32633481796.1at7e10ea4b3742f3d14bde086c2963118238fec3ab. - OS: Linux
7.0.0-30-generic, x86_64. - Node:
v24.18.0, ABI137. pnpm:9.15.0. - Codex:
codex-cli 0.150.1. Claude Code:2.1.247. - App:
11268. Server:19268. Host daemon:27268. - Data directory: the worktree-specific
$HOME/.bb-dev/bb-report-worktrees-bb-report-2384-revise-nk8aiy-c2e5ae4ce985directory.
4. Minimal reproduction
The test needs active Codex and Claude Code logins. It also needs Node 24.18.0 on PATH.
Run these exact commands from a clean directory.
git clone https://github.com/get-bb/bb.git /tmp/bb-2384 git -C /tmp/bb-2384 checkout --detach ad79bbb5ec909524f8f281e62d860c588a86f332 cd /tmp/bb-2384 test "$(node --version)" = "v24.18.0" test "$(node -p 'process.versions.modules')" = "137" pnpm install --frozen-lockfile --prefer-offline --package-import-method=copy pnpm exec turbo run build mkdir -p /tmp/bb-2384-repro curl -fsSL https://get-bb.github.io/reports/issues/2384/repro/commands.sh -o /tmp/bb-2384-repro/commands.sh curl -fsSL https://get-bb.github.io/reports/issues/2384/repro/server.ts -o /tmp/bb-2384-repro/server.ts curl -fsSL https://get-bb.github.io/reports/issues/2384/repro/package.json -o /tmp/bb-2384-repro/package.json bash /tmp/bb-2384-repro/commands.sh
The script copies the plugin into ./.report-private/. It then starts the isolated instance and installs the plugin.
The script runs the marker, stop, resume, and tool-call sequence on Codex and Claude Code. It also runs a fresh Codex control.
The exit trap stops the instance. It kills only worktree processes, deletes the matching data directory, and checks all three ports.
Read the script at commands.sh. Read the plugin at server.ts and package.json.
Actual resumed Codex output
{
"provider": "codex",
"threadId": "thr_i336pbgfks",
"initialOutput": "INITIAL_OK",
"resumedOutput": "dynamic_tool_resume_probe is unavailable."
}
Expected output and fresh control output
{
"threadId": "thr_4hfjm3yzyv",
"output": "DYNAMIC_TOOL_RESUME_OK"
}
Claude Code check on the base commit
{
"provider": "claude-code",
"threadId": "thr_wc6gbnb3sm",
"initialOutput": "INITIAL_OK",
"resumedOutput": "DYNAMIC_TOOL_RESUME_OK"
}
Results: live-repro.json. Sanitized wires: Codex and Claude Code.
5. Root cause
BB resolves the current tool list correctly. The server puts it in the resume context at thread-commands.ts. The host daemon forwards that list at thread.ts. The agent runtime builds a bridge thread/resume request at runtime.ts.
The Codex bridge adds the tool list to shared construction data. It spreads that data into the resume request. BB extends the start type locally to include dynamic tools. It does not extend the resume type.
The generated Codex resume contract has no dynamic-tool field. See ThreadResumeParams.ts. TypeScript accepts the extra field because the bridge uses an object spread. Codex app-server accepts the request, but it does not apply the extra field. The resumed rollout therefore keeps its original tool catalog.
The next turn/start request also has no dynamic-tool data. The agent then sees zero matching tools. This explains why the fresh control succeeds and the resumed thread fails.
Claude Code uses a different construction path. Its bridge builds a new MCP server from the current tool list at bridge.ts. Claude Code 2.1.247 applied that server during the test resume.
6. Proposed fix (first principles)
The correct fix is provider support for dynamic tools on thread/resume. Add that field to Codex app-server and replace the tool catalog during resume. Then update the generated Codex types and remove the untyped extra-field spread.
If Codex cannot support that operation, BB must use a new provider session when the tool catalog changes. The new session must preserve history through a supported fork or history input. BB must also report the new provider identity.
Persist a stable tool-catalog signature across runtime release. Compare the old signature with the selected resume catalog. Rebuild only when the signatures differ.
Add tests for tool addition, tool removal, schema changes, and same-name replacements. Run each test through start, stop, resume, and a real tool call. Increment the host daemon protocol version if the fix changes a daemon wire field.
Do not treat the current extra JSON field as provider support. The provider contract and the live result both reject that assumption.
7. Related issues
No confirmed duplicate was found. PR #2325 is related because it moved provider sessions to the current plugin bridge system.
The base commit contains no linked open pull request for issue #2384. No hostile PR review was required.
8. Appendix
Commands and checks
pnpm install --frozen-lockfile --prefer-offline --package-import-method=copy.pnpm exec turbo run build: 18 tasks passed.- The revised complete script ran one live Codex late-add test and one fresh Codex control.
- The same script ran one live Claude Code late-add test.
- Bridge traffic recorded with
BB_PROVIDER_BRIDGE_RECORD_DIR. git log ad79bbb5ec90..origin/mainfound no later Codex dynamic-tool fix.
Evidence policy
The report does not publish raw provider recordings. Those files contain paths, signed reasoning data, and unrelated session data. The linked NDJSON files contain only the fields that prove the result.
The NDJSON files came from the first live run. The updated JSON result came from the revised complete script.
Artifact list
2384/repro/commands.sh2384/repro/server.ts2384/repro/package.json2384/repro/live-repro.json2384/repro/codex-wire-evidence.ndjson2384/repro/claude-wire-evidence.ndjson
9. Verification
The independent verifier ran the original script after manual instance and plugin setup. Codex failed after resume, and the fresh Codex control passed.
The verifier also ran a separate Claude Code sequence. Claude Code returned DYNAMIC_TOOL_RESUME_OK after resume.
This revision added the omitted instance start, plugin copy, script call, and cleanup steps. It also added the full Claude Code sequence.
The revised script passed without manual instance or plugin setup. Its exit trap stopped the instance and deleted its data directory.
The final cleanup check showed that ports 11268, 19268, and 27268 were free.