Skip to content

fix(codex-api): handle Chat Completions DONE sentinel#8708

Merged
joshka-oai merged 1 commit intomainfrom
joshka/fix-chat-done-sentinel
Jan 5, 2026
Merged

fix(codex-api): handle Chat Completions DONE sentinel#8708
joshka-oai merged 1 commit intomainfrom
joshka/fix-chat-done-sentinel

Conversation

@joshka-oai
Copy link
Collaborator

Context

  • This code parses Server-Sent Events (SSE) from the legacy Chat Completions streaming API (wire_api = "chat").
  • The upstream protocol terminates a stream with a final sentinel event: data: [DONE].
  • Some of our test stubs/helpers historically end the stream with data: DONE (no brackets).

How this was found

  • GitHub Actions on Windows failed in codex-app-server integration tests with wiremock verification errors (expected multiple POSTs, got 1).

Diagnosis

  • The job logs included: codex_api::sse::chat: Failed to parse ChatCompletions SSE event ... data: DONE.
  • eventsource_stream surfaces the sentinel as a normal SSE event; it does not automatically close the stream.
  • The parser previously attempted to JSON-decode every data: payload. The sentinel is not JSON, so we logged and skipped it, then continued polling.
  • On servers that keep the HTTP connection open after emitting the sentinel (notably wiremock on Windows), skipping the sentinel meant we never emitted ResponseEvent::Completed.
  • Higher layers wait for completion before progressing (emitting approval requests and issuing follow-up model calls), so the test never reached the subsequent requests and wiremock panicked when its expected-call count was not met.

Fix

  • Treat both data: [DONE] and data: DONE as explicit end-of-stream sentinels.
  • When a sentinel is seen, flush any pending assistant/reasoning items and emit ResponseEvent::Completed once.

Tests

  • Add a regression unit test asserting we complete on the sentinel even if the underlying connection is not closed.

Context
- This code parses Server-Sent Events (SSE) from the legacy Chat
  Completions streaming API (wire_api = "chat").
- The upstream protocol terminates a stream with a final sentinel
  event: data: [DONE].
- Some of our test stubs/helpers historically end the stream with
  data: DONE (no brackets).

How this was found
- GitHub Actions on Windows failed in codex-app-server integration
  tests with wiremock verification errors (expected multiple POSTs,
  got 1).

Diagnosis
- The job logs included: codex_api::sse::chat: Failed to parse
  ChatCompletions SSE event ... data: DONE.
- eventsource_stream surfaces the sentinel as a normal SSE event; it
  does not automatically close the stream.
- The parser previously attempted to JSON-decode every data: payload.
  The sentinel is not JSON, so we logged and skipped it, then
  continued polling.
- On servers that keep the HTTP connection open after emitting the
  sentinel (notably wiremock on Windows), skipping the sentinel meant
  we never emitted ResponseEvent::Completed.
- Higher layers wait for completion before progressing (emitting
  approval requests and issuing follow-up model calls), so the test
  never reached the subsequent requests and wiremock panicked when its
  expected-call count was not met.

Fix
- Treat both data: [DONE] and data: DONE as explicit end-of-stream
  sentinels.
- When a sentinel is seen, flush any pending assistant/reasoning items
  and emit ResponseEvent::Completed once.

Tests
- Add a regression unit test asserting we complete on the sentinel
  even if the underlying connection is not closed.
@joshka-oai
Copy link
Collaborator Author

Original failure: https://github.com/openai/codex/actions/runs/20672803029/job/59356415589?pr=8693

  - FAIL codex-app-server::all suite::codex_message_processor_flow::test_send_user_turn_changes_approval_policy_behavior
  - FAIL codex-app-server::all suite::codex_message_processor_flow::test_codex_jsonrpc_conversation_flow
  - FAIL codex-app-server::all suite::codex_message_processor_flow::test_send_user_turn_updates_sandbox_and_cwd_between_turns
  - Verifications failed: - Mock #0. Expected range of matching incoming requests: == 4  Number of matched incoming requests:
    1
  - Failed to parse ChatCompletions SSE event: expected value at line 1 column 1, data: DONE

@joshka-oai
Copy link
Collaborator Author

https://github.com/openai/codex/actions/runs/20706657238/job/59438486220 on main:

possibly related?

     Summary [ 334.304s] 2670 tests run: 2667 passed (4 slow), 3 failed, 15 skipped
        FAIL [  22.152s] (  69/2670) codex-app-server::all suite::codex_message_processor_flow::test_codex_jsonrpc_conversation_flow
        FAIL [  22.023s] (  70/2670) codex-app-server::all suite::codex_message_processor_flow::test_send_user_turn_updates_sandbox_and_cwd_between_turns
        FAIL [  22.029s] (  71/2670) codex-app-server::all suite::codex_message_processor_flow::test_send_user_turn_changes_approval_policy_behavior

Copy link
Collaborator

@bolinfest bolinfest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great find!

Incidentally, support for the chat wire API is going away in February, but it's still better to have this fixed in the meantime!

#7782

@joshka-oai joshka-oai merged commit bba5e5e into main Jan 5, 2026
26 checks passed
@joshka-oai joshka-oai deleted the joshka/fix-chat-done-sentinel branch January 5, 2026 17:29
@github-actions github-actions bot locked and limited conversation to collaborators Jan 5, 2026
@joshka-oai
Copy link
Collaborator Author

Great find!

Codex did all the work diagnosing this. My steering was mainly to make sure it documented the problem well enough that it made sense and validating the findings.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants