Skip to content

feat(claude): keep the Chat tool catalog stable across ToolSearch loads (opt-in dispatcher) - #8502

Merged
lidge-jun merged 1 commit into
devfrom
codex/carry-8057-chat-tool-dispatch
Nov 10, 2026
Merged

lidge-jun merged 1 commit into
devfrom
codex/carry-8057-chat-tool-dispatch

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

On a translated Chat Completions route (DeepSeek, GLM and similar), each Claude Code ToolSearch load changes two parts of the next request: the tool-name list in the system message (src/adapters/tool-catalog-nudge.ts) and the top-level tools array. The provider's prefix cache then misses for almost the whole conversation. Claude Code v2.1.304+ turns tool search on behind a gateway by default, so ocx claude sessions hit this by default. #7453 fixed it for Responses destinations through additional_tools; the Chat wire has no such carrier.

This carries contributor PR #8057 (closed by its author without merging) onto current dev, as the owner decided on #7963, after a design review:

  • Opt-in only: claudeCode.chatToolDispatchModels takes the same selector grammar, validation, load-time salvage and provider-rename migration as appendOnlyToolLoadingModels. On a selected direct openai-chat route, the catalog carries one stable use_loaded_tool {name, arguments} dispatcher from the first request. A loaded deferred tool's definition is appended as text to the ToolSearch result that loaded it, which is the transcript tail at that moment. Earlier calls are re-encoded through the dispatcher, so earlier bytes never change.
  • Responses rewritten back: dispatcher calls in streaming and non-streaming responses are rewritten to the real tool before Claude Code sees them. Combo and policy attempts keep the original catalog. So do forced tool_choice on a hidden tool, an allowed_tools restriction, and duplicate call ids. The decision is made per physical adapter, so a fallback to a non-Chat target is unaffected.
  • Off means byte-identical: with the key absent, {}, [] or an unlisted selector, upstream requests are unchanged. Tests cover streaming and non-streaming, a request without ToolSearch, and a route listed only in appendOnlyToolLoadingModels.

Changes made during the carry, from the design review:

  1. src/types/config.ts was at 1,999 lines and the ratchet fails an unbaselined file at 2,000. The Claude tool-loading fields move to src/types/claude-tool-loading.ts, which OcxClaudeCodeConfig extends and both type barrels re-export (now 1,969 lines).
  2. A dispatcher call naming a tool that was not loaded (NotebookEdit, deferred) or not declared (Nope) used to reach Claude Code as use_loaded_tool, which Claude Code then reported as unknown. Every well-formed {name, arguments?} call is now rewritten to the named tool. Claude Code runs a declared tool exactly as a direct call, or reports the unknown tool by its real name. Malformed, extra-key, incomplete, empty-name and self-referencing calls pass through unchanged.
  3. The docs build on docs(claude): describe tool search behind a gateway for current Claude Code #8493: the troubleshooting entry names the opt-in next to the ENABLE_TOOL_SEARCH=false export, and English and Korean describe both mitigations. The "arguments are not streamed until the call ends" note is reworded, because that is true of every Chat tool call on dev, not a cost of this option.
  4. Shared selector helpers get neutral names. structure/data-planes/claude-tool-loading.md and its manifest entry now cover the Chat dispatcher.

Closes #7963

Co-authored-by: Gudals0320 255312450+Gudals0320@users.noreply.github.com

Verification

  • Fail-first for change 2: the dispatch tests went from 33 pass / 3 fail on the carried code to 37 pass / 0 fail. The client had received "name":"use_loaded_tool" with {"name":"NotebookEdit",…} as input.
  • The six tool-loading suites (claude-chat-tool-dispatch, claude-chat-tool-dispatch-routes, claude-inbound-tool-loading, claude-tool-loading-destinations, claude-inbound-tool-reference, claude-translated-tool-restrictions): 100 pass, 0 fail.
  • bun run typecheck, bun run structure:check, bun run privacy:scan: pass. Layout, layout tooling, file-size ratchet, Lab boundary and structure guards: 114 pass, 0 fail. tests/ci-workflows/docs-*.test.ts: 361 pass, 0 fail.
  • Merged with current dev (29dc0893f, which also edits structure/manifest.json): typecheck and structure:check pass, structure:index regenerates an identical INDEX.md, and the guards plus the dispatch suites pass (195 pass, 0 fail).
  • bun run test:changed (2,406 files, isolated HOME): 54,395 pass, 36 fail, 3 errors. The failures are the container's known environment failures, which fail identically on clean dev: local-control-socket (including the 3 ENOENT errors), real-npm Codex CLI update suites, codex-composed-acceptance, and the one catalog-omit-disabled-native case. One claude-cli-client-http case and ultrafast-tier-honesty failed only inside the combined run and pass alone on both this branch and origin/dev.
  • NOT RUN: a live DeepSeek or GLM session measuring cache hits (the contributor's measurements are on Claude Code ToolSearch loads re-miss the prompt cache on translated Chat routes; ocx claude now hits this by default #7963 and feat(claude): keep the Chat tool catalog stable across ToolSearch loads (opt-in dispatcher) #8057); full bun run test; macOS/Windows.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Maintainer judgment

  • Value: on translated Chat routes, ToolSearch loads stop invalidating the conversation prefix. The contributor measured about 97% cache hits with 0 breaks against 87.9% before.
  • UX change scope: one new opt-in config key. Nothing changes for anyone who does not set it.
  • Regression evidence for existing users: the byte-identity tests above, and the existing tool-loading suites pass unchanged.
  • Windows impact: none; request and response shaping only.
  • Verification scope / NOT RUN: see above.

…ds (opt-in dispatcher)

Maintainer carry of #8057 by @Gudals0320 onto current dev.

On a translated openai-chat route every client ToolSearch load rewrites the tool
catalog and the tool-name nudge in the system message, so the next request
re-misses the provider prompt cache for the whole conversation. The new opt-in
claudeCode.chatToolDispatchModels (same selector grammar, validation, salvage and
provider-ID migration as appendOnlyToolLoadingModels) applies only on a direct
openai-chat physical adapter: the catalog carries one stable use_loaded_tool
dispatcher from the first request, loaded definitions ride the ToolSearch result
as text, history is re-encoded deterministically, and dispatcher calls are
rewritten to the named tool on both parse paths. Absent, empty or unlisted
configuration sends byte-identical requests.

Changes on top of #8057:
- Every well-formed {name, arguments?} dispatcher call is rewritten to the tool
  it names, so a deferred tool that was never loaded, or an undeclared name,
  reaches Claude Code by its real name instead of as use_loaded_tool. Malformed,
  extra-key, incomplete, empty-name and self-named calls still pass through.
- toolSearch, appendOnlyToolLoadingModels and chatToolDispatchModels move to
  src/types/claude-tool-loading.ts, which OcxClaudeCodeConfig extends, so
  src/types/config.ts shrinks instead of crossing its 2,000-line cap.
- An allowed-tools restriction sends the request unprojected; the shared
  selector helpers get neutral names.
- The Chat-routes docs build on #8493's troubleshooting text and name the
  opt-in next to the ENABLE_TOOL_SEARCH=false mitigation.

Closes #7963

Co-authored-by: Gudals0320 <255312450+Gudals0320@users.noreply.github.com>
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner November 10, 2026 19:19
@lidge-jun lidge-jun added the ci:full Run the four full Ubuntu test shards on this pull request label Nov 10, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-10T19:21:52.908536Z 0a07759 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the enhancement New feature or request label Nov 10, 2026
@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@lidge-jun
lidge-jun merged commit 4bf2ec8 into dev Nov 10, 2026
30 of 33 checks passed
@lidge-jun
lidge-jun deleted the codex/carry-8057-chat-tool-dispatch branch November 10, 2026 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:full Run the four full Ubuntu test shards on this pull request enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude Code ToolSearch loads re-miss the prompt cache on translated Chat routes; ocx claude now hits this by default

1 participant