fix(tools): repair integral floats in native u64 tool fields - #2371
Conversation
Codex advertises multi_agent_v1__wait_agent's timeout_ms as a JSON Schema number, but its Rust runtime deserializes the field as u64. Grok serializes the integer through a float, so a wait of 120000 arrives as 120000.0 and Codex rejects the call before the tool runs, with an invalid-type error naming a floating point value where u64 was expected. The #1611 repair already existed but declined here, because it only fires on a declared integer. The schema lookup was never the problem: the error text comes from Codex's own deserializer, which only sees the call after the bridge emitted it. Treat a known native u64 field as integer-declared when the schema declares a numeric type, so the existing re-stringify path emits 120000. A fractional value is a real disagreement and still fails upstream. The allowlist is one field wide on purpose. It names only what has a captured u64 rejection, because the repair is unambiguous only for a field that cannot hold a fraction; a generic name like start, priority, or port would silently rewrite a third-party tool's legitimate fractional value. Cursor's sibling yield_time_ms is also declared number and is deliberately not included: it gets its own change when it gets its own reproduction. Array items are judged by their own schema rather than inheriting the key, so an array named like the allowlist is not rewritten. Closes #2316
|
✅ Deterministic PR hygiene checks passed. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe change adds scoped repair for integral ChangesNative u64 timeout repair
WP1 execution record
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CodexApp
participant bridgeToResponsesSSE
participant buildResponseJSON
participant coerceValue
participant wait_agent
CodexApp->>bridgeToResponsesSSE: send multi_agent_v1__wait_agent arguments
bridgeToResponsesSSE->>coerceValue: coerce timeout_ms
coerceValue-->>bridgeToResponsesSSE: return integer timeout_ms
bridgeToResponsesSSE->>wait_agent: execute repaired arguments
CodexApp->>buildResponseJSON: send multi_agent_v1__wait_agent arguments
buildResponseJSON->>coerceValue: coerce timeout_ms
coerceValue-->>buildResponseJSON: return integer timeout_ms
buildResponseJSON->>wait_agent: execute repaired arguments
Suggested reviewers: ✨ 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 |
Summary
Fixes #2316. Grok-routed Codex rejects
multi_agent_v1__wait_agentbefore the tool runs:Codex advertises
timeout_msas a JSON Schemanumber, but its Rust runtime deserializes the field asu64. Grok serializes the integer through a float, so a wait of120000arrives as120000.0.The #1611 integral-float repair already existed and was correctly wired — it just declined here, because
declaresIntegeronly fires on a declaredinteger(tool-argument-integers.ts:36-41).The schema lookup was never the problem. The error text comes from Codex's own deserializer, which only sees the call after the bridge emitted it — and the reported call is the namespaced name, so it resolved fine. That rules out the bare-name theory:
src/bridge.ts:1041and:1788reject undeclared names with a 502 before argument repair runs, so a schema registered under a bare key alone could never be consulted.So this is a one-file change: treat a known native u64 field as integer-declared when the schema declares a numeric type, and let the existing re-stringify path emit
120000.Why the allowlist is one field wide
U64_NUMBER_FIELDS = {"timeout_ms"}names only what has a capturedu64rejection. The repair is unambiguous only for a field that cannot legitimately hold a fraction — a generic name likestart,end,priority, orportwould silently rewrite a third-party tool's valid fractional value.Cursor's sibling
yield_time_ms(tool-definitions.ts:49) is also declarednumberand is deliberately excluded: no rejection has been captured against it. It gets its own change when it gets its own reproduction.This is the main difference from #2360, which fixes the same issue with a 10-name allowlist, repairs even when no schema is present, and re-adds a bare-name alias without the uniqueness guard at
collaboration.ts:154.Verification
Red-green proof — reverting only the source change fails exactly the three tests that pin the fix, and nothing else:
Nine tests cover: the reported call, a fractional value still failing honestly,
temperature: 1.0staying byte-identical, an already-clean payload preserving its bytes, nested objects, an array named like the allowlist not inheriting it, an allowlisted name over a string field, non-allowlisted fields keeping their floats, and both bridge paths end to end.Checklist
bun x tsc --noEmitcleanCloses #2316
Summary by CodeRabbit
Bug Fixes
Tests
Documentation