Skip to content

[Bug]: Responses passthrough UUID item IDs leave Codex turn stuck on Thinking #938

Description

@L14nY1Wang

Client or integration

Codex App

Area

Streaming

Summary

When an openai-responses passthrough provider emits UUID item IDs instead of canonical Responses prefixes, the upstream request completes and OpenCodex records HTTP 200 plus reported usage, but Codex App never produces an assistant item or completes the turn. The UI remains on Thinking until the user interrupts it.

This is distinct from #864. The stream contains a complete response.completed event, and switching macOS from the default relay path to streamMode: "eager-relay" does not change the result.

The incompatible IDs observed on the wire look like:

response.id  = b16df59f-bbc4-458f-80c6-eae4bac8dada
reasoning.id = 4ffa2f22-7283-43ae-ae18-7690736424c1
message.id   = 6d66799f-6b2b-4643-8d30-5176244a3a8b

Canonical OpenAI Responses item IDs use type prefixes such as resp_, rs_, and msg_. OpenCodex already has an opt-in responsesItemIdRepair, but it only repairs exact, preconfigured placeholder IDs or missing terminal IDs. It cannot repair randomly generated UUIDs that change on every request.

Reproduction

  1. Configure an API-key provider using adapter: "openai-responses" whose native Responses endpoint returns UUID IDs for response/message/reasoning objects.
  2. Set statelessResponses: true and route a model through it.
  3. Start a new Codex App turn with a minimal input such as Reply only OK.
  4. Observe that the provider returns the complete sequence:
response.created
response.output_item.added        # reasoning item with UUID id
response.reasoning_text.delta
response.output_item.done
response.output_item.added        # message item with UUID id
response.output_text.delta
response.output_text.done
response.output_item.done
response.completed                # status=completed, output and usage present
  1. OpenCodex logs the request as status: 200, usageStatus: "reported", and includes token usage from response.completed.
  2. Codex App remains on Thinking. Its rollout contains task_started and the user input, but no assistant response item and no successful turn completion. Interrupting the UI produces turn_aborted.

I also repeated the request after setting:

{"streamMode":"eager-relay"}

The Codex CLI JSON stream still emitted only thread.started and turn.started; it emitted neither an agent message nor turn.completed. This rules out the macOS tee() relay path as the cause.

Directly calling the same OpenCodex /v1/responses route with curl shows that the SSE stream and response.completed frame are complete; the differentiating protocol violation is the UUID item IDs.

Version

2.10.0

Operating system

macOS 26.5.2 (Darwin 25.5.0, Apple Silicon arm64)

Provider and model

OpenAI-compatible native Responses provider / deepseek-v4-flash and deepseek-v4-pro

Logs or error output

# OpenCodex persisted request summary
status=200
usageStatus=reported
inputTokens=14466
outputTokens=81
reasoningOutputTokens=49

# Codex rollout lifecycle
task_started
# no assistant response_item
# no turn_completed
turn_aborted reason=interrupted

Suggested fix

Extend ResponsesItemIdRepairConfig with a provider-local option such as:

{
  "responsesItemIdRepair": {
    "repairInvalidIds": true,
    "repairMissingTerminalIds": true
  }
}

In responses-item-id-repair.ts, when repairInvalidIds is enabled, rewrite any existing message or reasoning ID that does not start with its expected prefix:

const shouldRepair = state.placeholders[type].has(rawId)
  || (state.repairInvalidIds && !rawId.startsWith(REPAIRABLE_PREFIXES[type]));

const mapped = shouldRepair
  ? mintCanonicalId(type, state.scope, outputIndex)
  : state.repairMissingTerminalIds
    ? rawId
    : null;

The request-local mapping should be applied consistently to:

  • response.output_item.added.item.id
  • lifecycle event item_id fields
  • response.output_item.done.item.id
  • response.completed.response.output[].id

Function-call IDs and call_id should remain untouched. Because the rewrite is client-facing only, raw upstream snapshots can remain available for provider replay.

Redacted configuration

{
  "providers": {
    "provider": {
      "adapter": "openai-responses",
      "baseUrl": "https://redacted.example",
      "responsesPath": "/responses",
      "authMode": "key",
      "models": ["deepseek-v4-flash", "deepseek-v4-pro"],
      "statelessResponses": true
    }
  },
  "websockets": false
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions