Skip to content

[Bug]: OpenAI Responses replay can send overlong tool call_id values after tool use #74665

Description

@flashosophy

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

OpenAI Responses replay can send overlong or malformed call_id values back to the provider after a tool call, causing the next request to be rejected.

Steps to reproduce

  1. Install [email protected], which depends on @mariozechner/[email protected], or install @mariozechner/[email protected] directly.
  2. Use an OpenAI Responses provider path (openai-responses / openai-codex-responses) with a prior assistant tool call whose internal tool-call id is stored as {call_id}|{response_item_id}.
  3. Let the stored call_id or item id contain a long provider/internal id.
  4. Inspect the next Responses payload generated for replay after the tool result.

Minimal payload-capture repro:

TMP=$(mktemp -d)
cd "$TMP"
npm init -y >/dev/null
npm install @mariozechner/[email protected] >/dev/null
node --input-type=module <<'NODE'
import { streamOpenAIResponses } from '@mariozechner/pi-ai/openai-responses';

const longCallId = 'call_' + 'x'.repeat(120);
const longItemId = 'notfc_' + 'y'.repeat(120);
const toolCallId = `${longCallId}|${longItemId}`;

const model = {
  api: 'openai-responses',
  provider: 'openai-codex',
  id: 'gpt-test',
  baseUrl: 'https://example.invalid/v1',
  input: ['text'],
  output: ['text'],
  headers: {},
};

const context = {
  systemPrompt: 'test',
  messages: [
    {
      role: 'assistant',
      api: 'openai-responses',
      provider: 'openai-codex',
      model: 'gpt-test',
      content: [{ type: 'toolCall', id: toolCallId, name: 'noop', arguments: {} }],
    },
    { role: 'toolResult', toolCallId, content: [{ type: 'text', text: 'ok' }] },
    { role: 'user', content: 'continue' },
  ],
};

const stream = streamOpenAIResponses(model, context, {
  apiKey: 'x',
  onPayload(params) {
    console.log(JSON.stringify(params.input, null, 2));
    throw new Error('stop after payload capture');
  },
});

for await (const event of stream) {
  if (event.type === 'error') console.error(event.error.errorMessage);
}
NODE

Expected behavior

OpenClaw should normalize Responses tool identifiers before replaying them:

  • function_call.call_id and matching function_call_output.call_id should be valid Responses call ids, not raw overlong internal ids.
  • function_call.id should be a valid Responses item id and keep the required fc prefix/shape.
  • The replayed tool result should be accepted by OpenAI-compatible Responses APIs.

Actual behavior

The generated replay payload includes the raw overlong call id and raw item id:

[
  {
    "role": "system",
    "content": "test"
  },
  {
    "type": "function_call",
    "id": "notfc_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
    "call_id": "call_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "name": "noop",
    "arguments": "{}"
  },
  {
    "type": "function_call_output",
    "call_id": "call_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "output": "ok"
  },
  {
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "continue"
      }
    ]
  }
]

In real provider runs, this class of payload fails when the Responses API validates input[*].call_id / function-call ids.

OpenClaw version

2026.4.27-beta.1

Operating system

Ubuntu 24.04 / Linux

Install method

npm package / npm global beta path; isolated repro installed @mariozechner/[email protected], which [email protected] depends on.

Model

OpenAI Responses / OpenAI Codex Responses style model using tool calls.

Provider / routing chain

OpenClaw -> @mariozechner/pi-ai Responses provider -> OpenAI-compatible Responses endpoint.

Additional provider/model setup details

The repro uses onPayload to stop before network send, so no API credentials or live provider are required. The failure is in request construction/replay: stored tool-call ids are split and re-emitted without robust normalization.

Logs, screenshots, and evidence

$ npm view [email protected] dependencies.@mariozechner/pi-ai
0.70.5

$ npm view @mariozechner/[email protected] version
0.70.5

# Latest @mariozechner/pi-ai still has the same replay shape at 0.70.6:
# openai-responses-shared.js still splits toolCall.id and msg.toolCallId directly,
# and still stores stream function-call ids as `${item.call_id}|${item.id}`.

# Repro output shows overlong call_id and non-fc item id in the generated payload.

Relevant implementation shape in openai-responses-shared.js:

const [callId, itemIdRaw] = toolCall.id.split("|");
...
call_id: callId
...
const [callId] = msg.toolCallId.split("|");
...
call_id: callId
...
id: `${item.call_id}|${item.id}`

Impact and severity

Affected: users of OpenAI Responses / Codex Responses provider paths when a conversation continues after tool use and stored tool ids contain provider/internal ids that exceed Responses validation limits.

Severity: High for affected runs, because the conversation breaks immediately after tool execution even though the tool itself completed.

Frequency: Always reproduced for replay payload construction with the overlong ids shown above.

Consequence: Tool-using conversations fail on the follow-up request with provider-side validation errors such as invalid/overlong input[*].call_id.

Additional information

A local downstream patch fixes this by normalizing Responses call ids and item ids at all three boundaries:

  1. When converting stored assistant toolCall.id back into function_call input items.
  2. When converting toolResult.toolCallId into function_call_output.call_id.
  3. When storing streamed Responses function_call ids as OpenClaw tool-call ids.

The patch keeps ids deterministic, strips unsafe characters, caps length, and ensures Responses item ids have the expected fc prefix shape.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions