Skip to content

Streaming tool_calls produce duplicate arguments chunks #361

Closed

Description

@devuterian

Description

When using stream: true with tool calling, OpenCodex emits duplicate function.arguments chunks. OpenAI-compatible clients that accumulate argument deltas end up with corrupted JSON (concatenated duplicates), making tool calling unusable in streaming mode.

stream: false works correctly — each tool call has exactly one clean arguments JSON string.

Version

opencodex 2.7.35 (latest as of 2026-07-24)

Reproduction

curl -s -N http://127.0.0.1:10100/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "cursor/composer-2.5",
    "messages": [{"role": "user", "content": "Run ls /home/opc and read /home/opc/README.md. Do only tool calls, no text."}],
    "tools": [
      {"type": "function", "function": {"name": "shell", "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
      {"type": "function", "function": {"name": "read_file", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}}
    ],
    "stream": true
  }'

Actual behavior (streaming — BROKEN)

Each tool call produces 3 SSE chunks instead of 2:

# Tool call 0 (shell):
Chunk 1: {"index":0,"id":"tool_xxx","function":{"name":"shell","arguments":""}}
Chunk 2: {"index":0,"function":{"arguments":"{\"command\":\"ls /home/opc\"}","name":"shell"}}
Chunk 3: {"index":0,"id":"tool_xxx","function":{"name":"shell","arguments":"{\"command\":\"ls /home/opc\"}"}}  ← DUPLICATE

# Tool call 1 (read_file):
Chunk 4: {"index":1,"id":"tool_yyy","function":{"name":"read_file","arguments":""}}
Chunk 5: {"index":1,"function":{"arguments":"{\"path\":\"/home/opc/README.md\"}","name":"read_file"}}
Chunk 6: {"index":1,"id":"tool_yyy","function":{"name":"read_file","arguments":"{\"path\":\"/home/opc/README.md\"}"}}  ← DUPLICATE

Chunks 3 and 6 are duplicates — they repeat the full arguments already delivered in chunks 2 and 5, but wrapped in the complete tool_call object (with id field).

Downstream clients that follow the OpenAI streaming spec (accumulate function.arguments deltas) end up with:

{"command":"ls /home/opc"}{"command":"ls /home/opc"}  ← corrupt JSON

This causes "Unrepairable tool_call arguments" errors in agents like Hermes.

Expected behavior (non-streaming — WORKS)

{
  "tool_calls": [
    {
      "id": "tool_0dc837b1-...",
      "type": "function",
      "function": {
        "name": "shell",
        "arguments": "{\"command\":\"ls /home/opc\"}"
      }
    },
    {
      "id": "tool_9bbfae2a-...",
      "type": "function",
      "function": {
        "name": "read_file",
        "arguments": "{\"path\":\"/home/opc/README.md\"}"
      }
    }
  ]
}

Each arguments appears exactly once. ✅

Impact

Severity: Medium — affects any OpenAI-compatible client that accumulates function.arguments deltas in streaming mode. The workaround is to force stream: false.

Additional context

  • Cursor models affected: cursor/composer-2.5, cursor/composer-2.5-fast, cursor/auto
  • Likely all Cursor models that support tool calling
  • Non-streaming path is fine — the bug appears specific to the SSE streaming adapter

Activity

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions