Repository navigation
[Bug] Grok emits integer tool arguments as floats (120000.0) and routed calls are rejected before execution #1611
Description
Client or integration
Codex CLI
Provider or upstream service
xAI (xai/grok-4.5)
OpenCodex version
dev at 3982dae47 (observed live on the running service, 2.13.0)
Endpoint or capability
/v1/responses routed tool calls — integer tool arguments
Current behaviour
Grok serializes integer tool-call arguments as floats, and OpenCodex forwards the string through untouched. Codex then rejects the call before it ever runs, because its tool schemas declare those fields as integer types.
Every failure looks like this, and the tool never executes:
failed to parse function arguments: invalid type: floating point `120000.0`, expected u64
failed to parse function arguments: invalid type: floating point `29356.0`, expected i32
failed to parse function arguments: invalid type: floating point `12000.0`, expected usize
The .0 is present in the assembled arguments string that reaches the client, not something the client introduces. Extracted from a session rollout:
exec_command -> ["yield_time_ms":120000.0]
exec_command -> ["yield_time_ms":120000.0]
write_stdin -> ["session_id":29356.0, "yield_time_ms":60000.0]
write_stdin -> ["session_id":29356.0]
The model's intent is an integer in every case: yield_time_ms is a millisecond budget, and session_id is an exec session handle that was itself issued as 29356. Grok is round-tripping them through a float representation and emitting 29356.0.
This is not a JSON round-trip artifact inside OpenCodex. JSON.stringify(JSON.parse(...)) preserves integers exactly:
$ bun -e 'console.log(JSON.stringify(JSON.parse(String.raw`{"session_id":29356,"yield_time_ms":120000}`)))'
{"session_id":29356,"yield_time_ms":120000}
So the .0 arrives from upstream and survives because nothing on the routed path normalizes it. parseArgsObj in src/bridge.ts:246 only decides whether the arguments parse as an object; it does not reconcile argument values against the declared tool schema, and there is no integer coercion anywhere in src/adapters/ for tool-call arguments.
Expected behaviour
A float-valued argument whose declared schema type is integer should reach the client as an integer when the value is integral (Number.isInteger(120000.0) is true), so the tool call runs.
A non-integral value for an integer field is a genuine upstream error and should stay an error rather than being silently truncated.
Impact
Every affected turn is a hard failure, not a degradation: the tool call is rejected before execution, so the model loses the result and typically retries the same call with the same float. In the captured session this consumed five consecutive tool calls across exec_command and write_stdin without a single one running.
It is also transport-visible only on routed providers. The same Codex client works when the model emits real integers, which is why this reads as a Grok-specific tool-calling failure rather than a client bug.
Minimal redacted request or reproduction
- Route Codex CLI through OpenCodex at
xai/grok-4.5. - Ask for work that makes the model call a tool with an integer argument — any
exec_commandwhere it chooses ayield_time_ms, or anywrite_stdinreusing asession_id. - Observe the
function_call_outputfor that call.
Observed: failed to parse function arguments: invalid type: floating point 120000.0, expected u64, and the command does not run.
Expected: the call executes.
Scope check
Occurrences in local session rollouts are xai/grok-4.5 only. Five distinct values across two tool names (exec_command, write_stdin) and four Rust integer types (u64, i64, i32, usize), which is consistent with a serializer-level behavior on the provider side rather than one bad tool schema.
I have not verified whether other xAI models or other providers do the same; the sample here is one provider.
Logs or error output
{"type":"function_call_output","output":"failed to parse function arguments: invalid type: floating point `120000.0`, expected u64 at line 1 column 310"}
{"type":"function_call_output","output":"failed to parse function arguments: invalid type: floating point `120000.0`, expected u64 at line 1 column 305"}
{"type":"function_call_output","output":"failed to parse function arguments: invalid type: floating point `29356.0`, expected i32"}
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
coderabbitai commentedon Aug 13, 2026
🔗 Related PRs
#403 - feat(grok): auto-configure Grok Build and harden strict-client compatibility [merged]
#745 - fix: normalize tool function schemas to always have root type: "object" [open]
#781 - fix(bridge): cancel open tool calls on terminal error (
#765) [open]📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
fix(responses): repair integral-float integer tool arguments
lidge-jun commentedon Aug 13, 2026
Fixed on
devin 027a993.Your diagnosis held up end to end. Confirmed by reading the tree rather than assuming:
parseArgsObjonly decides whether the payload parses as an object, andrgforcoerce/Number.isIntegeracrosssrc/bridge.ts,src/server/responses/, andsrc/adapters/turns up nothing but unrelated HTTP-status checks. Nothing reconciled argument values against the declared schema, so the.0passed straight through.I took the coercion side, for the reason you gave:
120000.0has exactly one integer reading and the user currently has no recovery path. The boundary is intent — an integral float is a representation artifact and gets repaired;1.5in an integer field is a genuine disagreement with the schema and is left alone so it still fails, rather than being truncated into a plausible lie.Implementation (
src/lib/tool-argument-integers.ts):integer, including["integer","null"]unions andanyOf/oneOf/allOfbranchesnumber-typed fields, strings, or fields with no declared schemaitems, and local `` into$defs/`definitions`, with cycle and depth guards since provider tool schemas are untrusted inputMAX_SAFE_INTEGER, because a JS round-trip could not represent it exactly and would emit a silently different numberOne thing worth naming beyond the report: both emit paths needed it.
closeCurrentToolCallhandles streaming andflushToolCallhandles buffered, so a streaming-only fix would have left the non-streaming path broken. The schema map is built by reference inbuildToolBridgeMapsand rides the same options object that already carriesdeclaredToolNames, so this adds a map lookup rather than new plumbing.Verification:
120000.0,29356.0), including end-to-end assertions through the real bridge on both pathsbun run typecheckcleanOn your
rg -rnnote — you are right that it is not an OpenCodex defect, and thanks for isolating it rather than filing it. Worth remembering that-ris--replace, so-rnmeans "replace matches with the literal textn".fix(tools): repair bare integers in string-declared tool arguments
fix(tools): repair integral floats in native u64 tool fields
Merge from upstream (#8)
fix(responses): repair integral-float integer tool arguments
fix(tools): repair bare integers in string-declared tool arguments
fix(tools): repair integral floats in native u64 tool fields