Account Pooler: rebuild Codex WebSocket history from streamed output items - #3310
Closed
SawyerHood wants to merge 1 commit into
Closed
Account Pooler: rebuild Codex WebSocket history from streamed output items#3310SawyerHood wants to merge 1 commit into
SawyerHood wants to merge 1 commit into
Conversation
…items Codex's Responses WebSocket transport sends previous_response_id plus only the new input items, and the pooler reassembles the full history before forwarding one HTTPS request upstream. The pooler took the previous response's items from response.completed, but the ChatGPT Codex backend streams them only as response.output_item.done events and sends an empty output array in response.completed. Every incremental request therefore lost the previous response: within a turn the API rejected the orphaned tool output and Codex retried on a fresh socket, while at a turn boundary the request was accepted without the previous answer and the model answered the earlier question again. Accumulate response.output_item.done items in stream order and fall back to the completed event's output only when nothing was streamed. The integration test's fake upstream now streams items the way the backend does, and a unit test covers tool-call continuations, turn boundaries, and the fallback. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
Superseded: the same bug was fixed on main in #3307, and a follow-up will remove the WebSocket path from the pooler entirely. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human comments
What was wrong
Codex talks to the Account Pooler over its Responses WebSocket transport: after the first request on a socket it sends
previous_response_idplus only the new input items, and the pooler rebuilds the full history itself before forwarding one HTTPS request upstream. The pooler took the previous response's items from theresponse.completedevent, but the ChatGPT Codex backend streams them only asresponse.output_item.doneevents and sends"output": []inresponse.completed(verified with a direct probe through the pooler). Every incremental request therefore lost the previous response. Within a turn the API rejected the orphaned tool output with "No tool call found for custom tool call output" and Codex silently retried on a fresh socket, which is why Codex logs show a reconnect for almost every request. At a turn boundary the request is valid without the previous answer, so the model saw the earlier question unanswered and answered it again before the new one. Codex's own log database (~/.codex/logs_2.sqlite) records hundreds of these rejections per day since the WebSocket routing landed in #3056.What changed
plugins/account-pool/src/codex-websocket.tsaccumulatesresponse.output_item.doneitems in stream order for each forwarded response and uses them as the previous output when the next frame carriesprevious_response_id. It falls back to the completed event'soutputonly when no items were streamed. This mirrors how Codex builds its own delta baseline. No wire, CLI, or documentation changes.How you verified
plugins/account-pool/src/codex-websocket.test.tsdrives the handler with a fake hub: a tool-call continuation within a turn, a follow-up question across a turn boundary, and the completed-output fallback. The first two fail on the previous handler and pass now.response.output_item.doneand sends an emptyoutputinresponse.completed, matching the real backend, and its history assertion still holds.pnpm exec turbo run test typecheck lint --filter=bb-plugin-account-poolpasses.🤖 Generated with Claude Code