Codex CLI drops id and status fields from assistant messages when using Responses API, breaking multi-turn conversations

Open 💬 8 comments Opened Feb 24, 2026 by anencore94
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.104.0 (also reproduced on 0.88.0)

What subscription do you have?

N/A (custom model provider via OpenAI-compatible Responses API)

Which model were you using?

Reproduced with vLLM-backed models on OpenAI-compatible Responses API endpoints

What platform is your computer?

Darwin 24.6.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

ghostty 1.2.3

What issue are you seeing?

When Codex CLI is used against an OpenAI-compatible Responses API server (for example vLLM), multi-turn conversations fail because assistant messages are replayed into the next turn without id and status.

For assistant messages with output_text content, strict validators (for example Pydantic with OpenAI SDK response types) expect a ResponseOutputMessageParam shape that includes required fields such as id and status. Missing these fields causes large validation failures (for example many Input should be a valid string union-matching errors).

Observed behavior:

{
  "type": "message",
  "role": "assistant",
  "content": [
    { "type": "output_text", "text": "Hi!" }
  ]
}

This breaks strict multi-turn parsing on compatible servers.

What steps can reproduce the bug?

  1. Configure Codex CLI with a custom provider:
# ~/.codex/config.toml
model = "<your-model>"
model_provider = "custom"

[model_providers.custom]
base_url = "http://your-vllm-server/v1"
env_key = "API_KEY"
  1. Start a conversation (first turn succeeds).
  2. Send a second turn.
  3. The request fails with validation errors on strict servers (for example:

502 Bad Gateway: 178 validation errors and repeated Input should be a valid string).

Minimal local repro (no server needed):

from pydantic import BaseModel
from openai.types.responses import ResponseInputItemParam, ResponseOutputItem
from typing import TypeAlias

ResponseInputOutputItem: TypeAlias = ResponseInputItemParam | ResponseOutputItem

class TestRequest(BaseModel):
    input: str | list[ResponseInputOutputItem]

# Codex-like payload (fails)
TestRequest(input=[
    {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "hello"}]},
    {"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "Hi!"}]},
    {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "what?"}]}
])

# Payload with id/status preserved (succeeds)
TestRequest(input=[
    {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "hello"}]},
    {"type": "message", "role": "assistant", "id": "msg_001", "status": "completed", "content": [{"type": "output_text", "text": "Hi!"}]},
    {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "what?"}]}
])

What is the expected behavior?

Codex CLI should preserve id and status from assistant response messages when constructing subsequent turns' input arrays.

Expected assistant item shape:

{
  "type": "message",
  "role": "assistant",
  "id": "msg_example123",
  "status": "completed",
  "content": [
    { "type": "output_text", "text": "Hi!" }
  ]
}

Additional information

View original on GitHub ↗

8 Comments

etraut-openai contributor · 4 months ago

Can you say more about your use case? Which responses API are you using? It sounds like it may be making wrong assumptions.

anencore94 · 4 months ago

@etraut-openai Hi, as I mentioned already, the LLM API Server was self-hosted with vLLM. ("Reproduced with vLLM-backed models on OpenAI-compatible Responses API endpoints")
I think https://github.com/openai/codex/issues/12230 also have the same issue with the same situation.

Also, the self-hosted vLLM API Server returns with preserving 'id', and 'status' when I directly called to the vLLM API Server.

In the multi-turn conversation scenario, codex cli drop the fields from the previous turn's output when it flows to the next turn's input.
<img width="702" height="278" alt="Image" src="https://github.com/user-attachments/assets/4acf1c9d-6869-4e52-a1c7-6877ef12cb2b" />

etraut-openai contributor · 4 months ago

Ah, thanks for the clarification. I'm not familiar with vLLM. I recommend reporting this issue to the maintainers of that project. You can also try switching to ollama or LMStudio, which have responses API endpoints that work fine with Codex.

zhuangqh · 4 months ago
Ah, thanks for the clarification. I'm not familiar with vLLM. I recommend reporting this issue to the maintainers of that project. You can also try switching to ollama or LMStudio, which have responses API endpoints that work fine with Codex.

It's not vllm's fault. codex sending requests to backend didn't follow the /v1/responses API spec defined by openai. https://developers.openai.com/api/reference/resources/responses/methods/create
The API spec here clearly say that id and status fields are required.

anencore94 · 4 months ago
Ah, thanks for the clarification. I'm not familiar with vLLM. I recommend reporting this issue to the maintainers of that project. You can also try switching to ollama or LMStudio, which have responses API endpoints that work fine with Codex.

As @zhuangqh mentioned, it is from Codex. Codex didn't followed the OpenAI Responses API Schema. @etraut-openai
vLLM just did the validation check following OpenAI Responses API Schema.

bfroemel · 4 months ago
> Ah, thanks for the clarification. I'm not familiar with vLLM. I recommend reporting this issue to the maintainers of that project. You can also try switching to ollama or LMStudio, which have responses API endpoints that work fine with Codex. It's not vllm's fault. codex sending requests to backend didn't follow the /v1/responses API spec defined by openai. https://developers.openai.com/api/reference/resources/responses/methods/create The API spec here clearly say that id and status fields are required.

In support of this comment please note that simply making the offending fields optional in https://github.com/openai/openai-python "solves" the issue. vllm just depends on that official openai python library and uses its type definitions (coming directly from openai) for validation.

/openai-python$ git diff
diff --git a/src/openai/types/responses/response_output_message.py b/src/openai/types/responses/response_output_message.py
index 760d72d5..4f74a414 100644
--- a/src/openai/types/responses/response_output_message.py
+++ b/src/openai/types/responses/response_output_message.py
@@ -16,7 +16,7 @@ Content: TypeAlias = Annotated[Union[ResponseOutputText, ResponseOutputRefusal],
 class ResponseOutputMessage(BaseModel):
     """An output message from the model."""
 
-    id: str
+    id: Optional[str] = None
     """The unique ID of the output message."""
 
     content: List[Content]
@@ -25,7 +25,7 @@ class ResponseOutputMessage(BaseModel):
     role: Literal["assistant"]
     """The role of the output message. Always `assistant`."""
 
-    status: Literal["in_progress", "completed", "incomplete"]
+    status: Optional[Literal["in_progress", "completed", "incomplete"]] = None
     """The status of the message input.
 
     One of `in_progress`, `completed`, or `incomplete`. Populated when input items
diff --git a/src/openai/types/responses/response_output_text.py b/src/openai/types/responses/response_output_text.py
index 2386fcb3..d4406709 100644
--- a/src/openai/types/responses/response_output_text.py
+++ b/src/openai/types/responses/response_output_text.py
@@ -119,7 +119,7 @@ class Logprob(BaseModel):
 class ResponseOutputText(BaseModel):
     """A text output from the model."""
 
-    annotations: List[Annotation]
+    annotations: Optional[List[Annotation]] = None
     """The annotations of the text output."""
 
     text: str
diff --git a/src/openai/types/responses/response_reasoning_item.py b/src/openai/types/responses/response_reasoning_item.py
index 1a22eb60..f50fd54a 100644
--- a/src/openai/types/responses/response_reasoning_item.py
+++ b/src/openai/types/responses/response_reasoning_item.py
@@ -36,10 +36,10 @@ class ResponseReasoningItem(BaseModel):
     [managing context](https://platform.openai.com/docs/guides/conversation-state).
     """
 
-    id: str
+    id: Optional[str] = None
     """The unique identifier of the reasoning content."""
 
-    summary: List[Summary]
+    summary: Optional[List[Summary]] = None
     """Reasoning summary content."""
 
     type: Literal["reasoning"]
JaysonGeng · 3 months ago

I put together a fix for this here:
https://github.com/openai/codex/compare/main...JaysonGeng:codex:fix/preserve-responses-message-metadata

Root cause:

  • Codex was only restoring skipped output-item metadata for Azure store=true HTTP requests.
  • - Full-create replays on non-Azure Responses endpoints and websocket response.create requests were still dropping assistant message metadata.
  • - - Strict Responses validators then rejected replayed assistant messages because they were missing id and status.

What changed:

  • preserve replay metadata for Responses HTTP requests regardless of provider
  • - preserve the same metadata for websocket response.create bodies
  • - - synthesize status: "completed" for replayed assistant messages so they remain valid output-item inputs on full-create requests
  • - - - add HTTP + websocket regression coverage

Validation:

  • cargo test -p codex-api openai_preserves_assistant_message_id_and_status_in_request_body -- --exact
  • - CARGO_INCREMENTAL=0 cargo test -p codex-core responses_websocket_v2_creates_without_previous_response_id_when_non_input_fields_change -- --exact
  • - - just fmt
  • - - - CARGO_INCREMENTAL=0 just fix -p codex-api
  • - - - - CARGO_INCREMENTAL=0 just fix -p codex-core
  • - - - - - git diff --check

Note: just argument-comment-lint still isn’t runnable in this environment because bazel is not installed.

Upstream PRs are still invitation-only from my account, so I pushed this to my fork and can open a PR immediately if an OpenAI maintainer sends an invite or prefers a different handoff.

DotDawson · 2 months ago

This bug is the reason I am still using Claude Code. This bug prevents Codex from working with providers that correctly implement the responses API spec. Please fix Codex so it aligns with the official responses API spec.