Repository navigation
[Compatibility] cursor emits string tool arguments as bare integers (cell_id: 4) and routed calls are rejected before execution - mirror of #1611 #1938
Description
Client or integration
Other
Provider or upstream service
Codex-CLI
OpenCodex version
2.24.1
Endpoint or capability
/v1/chat/completions and /v1/responses - tool calls
Current behaviour
A JSON integer emitted by the provider in a string-declared field passes through the bridge untouched. Codex then rejects the call before the tool runs. Affected in practice: Codex's code-mode wait tool (cell_id declared {"type": "string"}). Through cursor/gpt-5.6-sol, every wait call arrives as {"cell_id":4,...} and fails. Measured across 3 full Codex sessions: 19/19 wait calls rejected. The model never self-corrects (it retries varying yield_time_ms/max_tokens), so this is a hard failure loop, not a degradation - same failure shape as #1611. Prompt-level mitigation does not work: with an explicit rule in context ("cell_id is a STRING, always quote it") the provider still emitted the integer on every call.
Expected behaviour
The proxy repairs the representation artifact against the declared schema, as it already does for the mirror case: #1611 fixed integer-declared fields arriving as integral floats (120000.0 -> 120000). A JSON integer in a string-declared field has exactly one faithful string reading (4 -> "4"), so it can be repaired under the same boundary logic; any non-integer value in a string field stays untouched and still fails, so no plausible lie is manufactured.
Minimal redacted request or reproduction
# Proxy running on the default port. Tool schema declares cell_id as a string.
curl -s http://127.0.0.1:10100/v1/chat/completions -H 'Content-Type: application/json' -d '{
"model": "cursor/gpt-5.6-sol",
"messages": [
{"role": "system", "content": "You manage exec cells. When a script yields, resume it with the wait tool."},
{"role": "user", "content": "Script running with cell ID 4. Resume it and get its output."}
],
"tools": [{
"type": "function",
"function": {
"name": "wait",
"description": "Waits on a yielded exec cell and returns new output or completion.",
"parameters": {
"type": "object",
"properties": {
"cell_id": {"type": "string", "description": "Identifier of the running exec cell."},
"yield_time_ms": {"type": "number"},
"max_tokens": {"type": "number"}
},
"required": ["cell_id"],
"additionalProperties": false
}
}
}],
"tool_choice": "required",
"max_tokens": 200
}'Actual response or error
Tool-call arguments returned by the proxy (schema declares cell_id as string):
{"cell_id":4,"yield_time_ms":30000,"max_tokens":4000}
When this reaches Codex (code-mode `wait` tool, same schema), the call is rejected before the tool runs:
failed to parse function arguments: invalid type: integer `4`, expected a string at line 1 column 12Upstream documentation
https://platform.openai.com/docs/guides/function-calling (tool-call arguments must conform to the declared JSON Schema; strict mode guarantees it). No cursor-specific public spec: expected behaviour is the concrete client requirement of Codex CLI 0.147.0, which parses wait.cell_id as a Rust String and rejects integers.
Suggested mapping or implementation notes
src/lib/tool-argument-integers.ts already does schema-aware repair for integral floats in integer-declared fields, wired at both bridge flush points (streaming and non-streaming). Extending coerceValue with the symmetric string case covers this: when the resolved schema declares "string" (directly or via anyOf/oneOf/allOf branches) and the value is a JSON integer, re-emit it as its decimal string. The cheap-reject regex at the top of coerceIntegerToolArguments (/\d\.\d/) would also need to admit integer payloads for string-declared parameters.
Additional context and attachments
Mirror of #1611 (closed): same provider-side serialization-artifact class, opposite direction. Also checked 2.24.2: tool-argument-integers.ts unchanged, case still uncovered.
Checks
- I searched existing provider and compatibility issues.
- The request and response were redacted.
- The expected behaviour is based on an upstream specification or a concrete client requirement.
Activity
lidge-jun commentedon Aug 18, 2026
Closing as part of the Cursor sweep — #1993 / #1994 / #1995 are on
dev(merge4f72d6f75).Being precise about coverage: what landed is the tool-call lifecycle (truncated turns, cancel provenance, terminal-less turns reported as
completed), not type coercion of tool arguments. A provider emittingcell_id: 4where the schema declares{"type": "string"}is a separate defect on a separate path, and it is the same shape as #1611.The merged work does make its failure mode honest rather than silent — a turn that dies before the tool runs now reports as failed instead of coming back
completed— but the rejected call itself is untouched.Closing to reflect the merged state. If you still see integer-for-string arguments on
devat4f72d6f75, reopen and I will treat it as its own unit; thecell_idreproduction is specific enough to work from directly.lidge-jun commentedon Aug 18, 2026
Reopening. The close comment explicitly says the integer-for-string coercion (
cell_id: 4against{"type":"string"}) was not touched by the merged lifecycle work and the rejected call is unchanged — that is the entire defect this issue tracks, socompletedwas a state error. This should close via a small PR extending the existing schema-aware repair insrc/lib/tool-argument-integers.tsto coerce bare integers to strings when the declared schema type isstring, with a regression test on thecell_idreproduction.eliasprunaire commentedon Aug 18, 2026
Thanks for the precise close/reopen : the coverage boundary you drew matches what I see.
Confirming the defect is still present:
src/lib/tool-argument-integers.tsat4f72d6f75ondevis identical (ignoring comments) to the file shipped in the 2.24.x releases ; no string branch incoerceValue, so integer-for-string arguments still pass through untouched.{"cell_id":4,"yield_time_ms":1000,"max_tokens":4000}withcell_iddeclared{"type": "string"}.Happy to verify the fix with the same reproduction (plus a full Codex session counting
waitrejections, currently 19/19 across 3 sessions) as soon as the coercion PR lands.Hope this helps
lidge-jun commentedon Aug 18, 2026
Fixed on
dev: #2005 merged. The schema-aware repair insrc/lib/tool-argument-integers.tsnow covers the mirror case — a safely-integral JSON number in a string-only declared field is re-serialized as its single faithful string reading (cell_id: 4→"4"), with unions that accept numeric types left untouched, non-integral values still failing honestly, and 2^53+ values keeping their original bytes. Regression tests cover the exactcell_idreproduction from this report. Reacheslatestwith the next release train.fix(tools): repair bare integers in string-declared tool arguments
fix(tools): repair bare integers in string-declared tool arguments