Bug: ToolName namespace collision causes 'unsupported custom tool call: execexec' with gpt-5.6-sol (HIGH-PRIORITY)

Open 💬 1 comment Opened Jul 11, 2026 by MayukXT

Effect: The GPT 5.6 models are unusable as they are having issues with tool calling.

Problem

Models using use_responses_lite (e.g. gpt-5.6-sol) emit ResponseItem::CustomToolCall events where the namespace field equals the tool name:

{ "name": "exec", "namespace": "exec", "call_id": "call_abc123", "input": "..." }

The built-in exec handler is registered under ToolName::plain("exec") (namespace: None). When the router constructs a ToolName from the response, it creates ToolName { name: "exec", namespace: Some("exec") }, which doesn't match. The registry lookup fails and the Display impl concatenates namespace + name:

unsupported custom tool call: execexec

This kills the tool execution bridge for the entire session.

Example GPT 5.6 Sol (Ultra) responses:

  • I hit a tooling hiccup on my first read command. Retrying with the shell directly so I can keep moving on the repo

inspection.`

  • I’m checking the tool surface itself for direct shell access. The collaboration tools are fine; the shell wrapper is

the piece misbehaving, so I’m trying the underlying command entrypoint directly.`

And then it just fails to do anything, ofcourse.
---

Root Cause

ToolName::new(), ToolName::namespaced(), and the derived Deserialize impl preserve the namespace field verbatim from the wire format. There is no normalization to strip a redundant namespace that equals the tool name.

---

Fix (ready to merge)

Affects: codex-rs/protocol/src/tool_name.rs, codex-rs/core/src/tools/router.rs

Normalize ToolName at construction time — when namespace == name, set namespace to None:

| Entry Point | Change |
|---|---|
| ToolName::new() | .filter(\|ns\| ns != &name) |
| ToolName::namespaced() | if namespace == name { None } else { Some(namespace) } |
| Deserialize impl | Replace derived with manual impl delegating to ToolName::new() |

The fix is implemented and tested in my fork:
➡️ Branch: MayukXT/codex:fix/tool-namespace-collision-regression
➡️ Diff: main...MayukXT:fix/tool-namespace-collision-regression

---

Test Results

Unit tests (codex-protocol)test_tool_name_normalization:

  • Covers plain, distinct-namespace, same-namespace construction, and JSON deserialization
  • All 241 protocol tests pass ✅

Integration test (codex-core)build_custom_tool_call_normalizes_equal_namespace_and_name:

  • Feeds CustomToolCall { namespace: Some("exec"), name: "exec" } through ToolRouter::build_tool_call()
  • Asserts output is ToolName::plain("exec")
  • All 328 core tool tests pass ✅

E2E:

  • Built CLI from source, ran codex exec "..." with gpt-5.6-sol
  • Model emitted exec/exec custom tool calls — fix normalized them, router dispatched correctly, no error ✅

---

Backward Compatibility

  • Serialize is untouched — wire format output unchanged
  • Only namespace == name collapses to None; MCP tools like mcp__python/exec are unaffected
  • ToolName::plain() bypasses the filter entirely

---

Fix authored by @MayukXT

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗