fix(codex): keep direct MCP tools visible for routed models - #1529
Conversation
📝 WalkthroughWalkthroughRouted catalog entries now set ChangesRouted search capability
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
✅ Deterministic PR hygiene checks passed. |
|
Exact-head GitHub CI is now fully green on The PR remains draft pending independent maintainer review. I am not self-approving or self-merging it. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/codex/catalog/sync.ts`:
- Line 316: Update the fallback catalog-row construction around the isRouted
spread so Cursor routes do not receive web_search_tool_type or
supports_search_tool. Reuse normalizeRoutedCatalogEntry if applicable, or gate
the hosted-search fields on !slug.startsWith("cursor/"), while preserving the
shared provider capability contract. Add a regression test covering a Cursor
fallback entry.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 31808470-360e-46b3-b101-c10bd042376b
📒 Files selected for processing (6)
src/codex/catalog/parsing.tssrc/codex/catalog/sync.tsstructure/03_catalog-and-subagents.mdtests/catalog-cursor-search.test.tstests/codex-catalog.test.tstests/e2e-style/phase100-native-parity.test.ts
| shell_type: "shell_command", visibility: "list", supported_in_api: true, | ||
| priority, base_instructions: "You are a helpful coding assistant.", | ||
| ...(isRouted ? { web_search_tool_type: "text_and_image", supports_search_tool: true } : {}), | ||
| ...(isRouted ? { web_search_tool_type: "text_and_image", supports_search_tool: false } : {}), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not advertise hosted search on Cursor fallback rows.
If template === null and model describes a cursor/... route, this spread adds web_search_tool_type: "text_and_image". The normal path in src/codex/catalog/parsing.ts Lines 395 through 405 removes that field for Cursor entries. Cursor bypasses the hosted-search sidecar, so the fallback catalog can advertise an unavailable capability.
Reuse normalizeRoutedCatalogEntry in this fallback path, or condition web_search_tool_type on !slug.startsWith("cursor/"). Add a regression test for a Cursor fallback entry.
As per path instructions, this src/** change must preserve the shared provider capability contract. The supplied PR objective defines Cursor rows as sidecar-free.
🧰 Tools
🪛 ast-grep (0.45.1)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFileSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/codex/catalog/sync.ts` at line 316, Update the fallback catalog-row
construction around the isRouted spread so Cursor routes do not receive
web_search_tool_type or supports_search_tool. Reuse normalizeRoutedCatalogEntry
if applicable, or gate the hosted-search fields on !slug.startsWith("cursor/"),
while preserving the shared provider capability contract. Add a regression test
covering a Cursor fallback entry.
Source: Path instructions
supports_search_tool=false (lidge-jun#1529) fixed plugin visibility by paying a measured 2.7x turn-1 payload tax: under tool_mode=code_mode_only every MCP declaration renders into exec.description (16,965 -> 175,849 chars, total 96,699 -> 258,929). Reachability never needed the tax: code mode keeps Deferred MCP tools callable via exec's tools global / ALL_TOOLS without a tool_search round-trip (upstream code_mode suite; live canary 2026-08-13, routed kimi/k3 executed tools.mcp__node_repl__js). - parsing.ts: non-Cursor routed rows advertise supports_search_tool=true; Cursor stays false (runTurn bypasses the sidecar) - sync.ts deriveEntry fallback: same split (Cursor fallback keeps false and drops web_search_tool_type) - tests: dual-seam pair fence (code_mode_only + search=true) on template and template-less paths, plus Cursor fallback inverse - structure/03: decision log updated Measurement + canary: devlog/_plan/260813_tool_catalog_deferral/010,020
…P3 gaps A second audit round found that my own "closures" were partly overstated. Fixed by doing the work, not by rewording it. Malformed-load warning (devlog 020, previously missing entirely). The schema degrades these fields with `.catch(undefined)` so a typo cannot cost a user their providers or credentials, but silent degradation is its own failure: an operator whose emergency escape hatch was dropped would never learn it is inactive — the #1529 observability failure at smaller scope. Adds `warnDegradedRoutedToolDiscoveryForLoad`, wired into both load paths beside the existing `retryOn429` sanitizer and following its conventions: runs before schema validation, redacts secret-shaped provider/model names, and logs only the received TYPE, never the value, since provider config can hold secrets. Backward compatibility, previously claimed but not proven. The old tests called `validateConfigCandidate()`, which cannot demonstrate a file-level promise. They now drive the real on-disk round trip through `loadConfig`/`saveConfig` against a temp `OPENCODEX_HOME`: - a pre-field config gains no persisted fields on read, and an unrelated save does not introduce them; - a config carrying both fields survives an unrelated save, which is the `.passthrough()` downgrade contract. Ablation: with the fields removed from the schema AND `.passthrough()` switched to `.strict()`, the downgrade test fails. It is not vacuous. Combo alias coverage (devlog 025), which the audit correctly refused to let me defer since it sits inside WP3's declared combo scope. A direct policy is now asserted through every alias shape — default `combo/<id>`, bare, slashed, and explicit native alias — because the alias is what Codex sees, and a shape-dependent policy is exactly the defect class the unified Cursor fence already had to fix once. 029 exit gate corrected. It had been edited to claim the `020` differential was the only open item. That was false: the `023` prior-build comparison and the `025` forcing-member diagnostic remain open too. The retracted claim is left visible in the note on purpose — this unit exists because a plan asserted more verification than it had, and quietly editing that away would repeat it. Verification: bun x tsc --noEmit clean; 427 pass / 0 fail across 9 suites (49 in the focused file); privacy:scan passed; SHA256SUMS regenerated and verified.
Summary
supports_search_tool: falseso Codex App keeps compatible direct MCP/plugin tools visibleweb_search_tool_type: "text_and_image"capability for non-Cursor routestool_searchparser and bridge intact for clients that explicitly send that wire shapeCloses #1522
Verification
bun scripts/test.ts tests/catalog-cursor-search.test.ts tests/codex-catalog.test.ts tests/e2e-style/phase100-native-parity.test.ts(176 passed, 0 failed)env -u OPENCODEX_API_AUTH_TOKEN bun scripts/test.ts tests/codex-shim.test.ts(67 passed, 0 failed; confirms the unrelated full-suite host-token interference is environmental)bun run typecheckbun run privacy:scangit diff --checkbun run testprogressed through the affected catalog and broad integration suites without a product assertion failure, but the long-running process was externally terminated with SIGTERM/143 before completion; exact-head repository CI remains required before merge.Checklist
Summary by CodeRabbit
Bug Fixes
Documentation