Skip to content

[Compatibility] cursor emits string tool arguments as bare integers (cell_id: 4) and routed calls are rejected before execution - mirror of #1611 #1938

Closed

Description

@eliasprunaire

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 12

Upstream 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

  1. lidge-jun commented on Aug 18, 2026

    @lidge-jun
    Owner

    Closing as part of the Cursor sweep — #1993 / #1994 / #1995 are on dev (merge 4f72d6f75).

    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 emitting cell_id: 4 where 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 dev at 4f72d6f75, reopen and I will treat it as its own unit; the cell_id reproduction is specific enough to work from directly.

  2. lidge-jun commented on Aug 18, 2026

    @lidge-jun
    Owner

    Reopening. The close comment explicitly says the integer-for-string coercion (cell_id: 4 against {"type":"string"}) was not touched by the merged lifecycle work and the rejected call is unchanged — that is the entire defect this issue tracks, so completed was a state error. This should close via a small PR extending the existing schema-aware repair in src/lib/tool-argument-integers.ts to coerce bare integers to strings when the declared schema type is string, with a regression test on the cell_id reproduction.

  3. eliasprunaire commented on Aug 18, 2026

    @eliasprunaire
    Author

    Thanks for the precise close/reopen : the coverage boundary you drew matches what I see.

    Confirming the defect is still present:

    • Static: src/lib/tool-argument-integers.ts at 4f72d6f75 on dev is identical (ignoring comments) to the file shipped in the 2.24.x releases ; no string branch in coerceValue, so integer-for-string arguments still pass through untouched.
    • Live: re-ran the reproduction from the issue body today against the running proxy (2.24.1); the tool call still comes back as {"cell_id":4,"yield_time_ms":1000,"max_tokens":4000} with cell_id declared {"type": "string"}.

    Happy to verify the fix with the same reproduction (plus a full Codex session counting wait rejections, currently 19/19 across 3 sessions) as soon as the coercion PR lands.

    Hope this helps

  4. lidge-jun commented on Aug 18, 2026

    @lidge-jun
    Owner

    Fixed on dev: #2005 merged. The schema-aware repair in src/lib/tool-argument-integers.ts now 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 exact cell_id reproduction from this report. Reaches latest with the next release train.

  5. added a commit that references this issue on Aug 18, 2026
    cc54279
  6. added a commit that references this issue on Sep 17, 2026
    faa6fcb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions