GPT-5.6 Code Mode omits tool_search from exec, degrading deferred MCP discovery
Summary
GPT-5.6 model metadata selects code_mode_only while supporting deferred tool search. The Code Mode converter currently drops ToolSpec::ToolSearch, so the public exec tool does not expose the dedicated BM25 tools.tool_search(...) discovery primitive.
Deferred tools are still reachable through manual ALL_TOOLS filtering, but GPT-5.6 loses the ranked discovery path that GPT-5.5 Direct mode retains. In practice this reduces proactive selection of specialized deferred MCP tools such as CodeGraph and encourages fallback to generic shell search.
Environment
- Codex CLI:
codex-cli 0.144.0-alpha.4 - Upstream Codex checked:
656a2d0905c9e0b9bdade1badab07ef6d42ca17c - OS: macOS 26.3.1, arm64
- Model: GPT-5.6 (
tool_mode = code_mode_only,supports_search_tool = true) - Comparison model: GPT-5.5 (
tool_mode = null, Direct mode)
Repository Decision
- Target repository:
openai/codex - Why this belongs here: the omission is in the upstream
ToolSpecto Code Mode conversion and reproduces independently of the MCP provider. - Upstream evidence: current
codex-rs/tools/src/code_mode.rsconvertsToolSpec::ToolSearch { .. } | ToolSpec::WebSearch { .. }toVec::new().
Reproduction
- Use a model with
tool_mode = code_mode_onlyandsupports_search_tool = true(current GPT-5.6 metadata), with at least one searchable deferred MCP namespace. - Start a turn and inspect the nested declarations in the public
exectool. - Observe that deferred MCP metadata is available through
ALL_TOOLS, but there is notools.tool_search(...)declaration. - The converter behavior is also locked by the current upstream unit test:
``bash``
cargo test -p codex-tools \
tool_spec_to_code_mode_tool_definition_skips_unsupported_variants \
-- --nocapture
It passes by asserting that converting ToolSpec::ToolSearch returns None.
Expected Behavior
When search support causes MCP tools to be deferred, Code Mode should expose tool_search as a nested function inside exec. Individual deferred MCP schemas should remain absent from the initial prompt.
Actual Behavior
The planner correctly creates and registers ToolSearchHandler, but both the Code Mode prompt-definition and runtime-definition paths pass the spec through collect_code_mode_*_tool_definitions. ToolSpec::ToolSearch is discarded there, leaving only the less capable instruction to manually filter ALL_TOOLS by name and description.
Evidence
codex-rs/models-manager/models.json: GPT-5.6 Sol/Terra/Luna usecode_mode_onlywith search support; GPT-5.5 does not force Code Mode.codex-rs/core/src/mcp_tool_exposure.rs: searchable MCP tools are intentionally deferred.codex-rs/core/src/tools/spec_plan.rs: the deferred runtimes andToolSearchHandlerare registered before Code Mode conversion.codex-rs/tools/src/code_mode.rs:ToolSpec::ToolSearchis converted to an empty definition list.codex-rs/core/src/tools/code_mode/execute_handler.rs: the same conversion determines which nested tools are executable in V8.- Existing integration coverage confirms deferred tools can still be called using
ALL_TOOLS.find(...); the defect is specifically the missing ranked search primitive, not total tool unreachability.
Local live A/B evidence: before the patch, a GPT-5.6 repository-graph lookup fell back to rg without invoking CodeGraph. With the patch, the same path invoked codegraph_explore first and completed without an rg fallback.
Related but distinct issues:
- #31894: Responses Lite does not expose the public
exec/ Code Mode tool at all. - #32086: Responses Lite hides a top-level
tool_searchinsideadditional_toolsfor deferred V1 multi-agent tools. - #21503: BM25 exact-name ranking after
tool_searchis successfully invoked.
Root Cause
The deferred-tool planner assumes the search primitive will remain available when it defers MCP schemas. That assumption is true in Direct mode, but Code Mode treats the hosted ToolSpec::ToolSearch variant as unsupported and removes it from both the generated exec guide and the V8 runtime catalog.
Two adjacent bridges are also needed once the definition is preserved: Code Mode invokes nested functions as ToolPayload::Function, while ToolSearchHandler originally accepts only ToolPayload::ToolSearch; and the search result needs a Code Mode result shape containing the serialized LoadableToolSpec[].
Proposed Fix
- Convert
ToolSpec::ToolSearchinto a Code Mode function definition using its existing description and parameter schema. - Accept the Code Mode
ToolPayload::Functionform inToolSearchHandlerand parse it asSearchToolCallParams, while preserving the native hosted-tool path. - Return the serialized
LoadableToolSpec[]throughToolOutput::code_mode_result. - Keep hosted
WebSearchbehavior unchanged and keep individual deferred MCP schemas out of the initialexecdescription.
Reference implementation:
I am sharing the commit as implementation context rather than opening an unsolicited PR. If this approach aligns with the intended architecture, I would be happy to open an invited PR, or the commit can be cherry-picked directly.
Verification Plan
- Unit:
ToolSpec::ToolSearchconverts to an augmented Code Mode function definition. - Output: Code Mode receives a JSON array of loadable tool specs while the native
ToolSearchOutputresponse remains unchanged. - Integration: in
CodeModeOnly,execdocumentstools.tool_search(...), search returns a non-empty result, and the still-deferred MCP tool remains callable without leaking its schema into the initial prompt. - Live: repeat a GPT-5.6 graph-shaped repository query and verify CodeGraph is selected without an
rgfallback.
Local verification on the reference commit:
cargo test -p codex-tools tool_spec_to_code_mode_tool_definition_supports_tool_search
1 passed; 0 failed
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all code_mode_only_guides_all_tools_search_and_calls_deferred_app_tools
1 passed; 0 failedThis issue has 2 comments on GitHub. Read the full discussion on GitHub ↗