← reports

#2384 · Conditionally selected plugin tool is not added when a released thread resumes

Bug Priority: Medium Effort: not set providers plugins open on GitHub 2026-08-27 · base ad79bbb5ec909524f8f281e62d860c588a86f332

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 issueStatusEvidence
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

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

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

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.