← Back to PRs

#16994: fix(gateway): prevent double terminal SSE event on OpenResponses error

by AI-Reviewer-QS open 2026-02-15 09:17 View on GitHub →
gateway stale size: XS
## Summary - Fix a bug where streaming error responses sent **two** terminal SSE events (`response.failed` followed by `response.completed`) instead of exactly one - The catch block now sets `closed=true` and properly closes the SSE stream after writing `response.failed`, preventing the lifecycle event listener from triggering a second terminal event via `maybeFinalize` ## Root Cause In `src/gateway/openresponses-http.ts`, when `agentCommand` throws during streaming mode: 1. The catch block wrote a `response.failed` SSE event 2. Then emitted a lifecycle `error` event via `emitAgentEvent` 3. The `onAgentEvent` listener caught `phase === "error"` and called `requestFinalize("failed", ...)` 4. `requestFinalize` -> `maybeFinalize` wrote a second terminal event (`response.completed` with failed status) The OpenResponses spec expects exactly one terminal event per response stream. ## Fix - Set `closed = true` and call `unsubscribe()` in the catch block before writing `response.failed` - Remove the lifecycle error event emission (which was only triggering the duplicate) - Properly close the SSE stream with `writeDone` + `res.end()` in the catch block - The `finally` block's `!closed` guard prevents it from emitting lifecycle `end` after an error ## Test plan - [x] All 377 existing gateway unit tests pass - [x] Linter and formatter pass with no warnings - [ ] Verify that streaming error responses produce exactly one `response.failed` terminal event - [ ] Verify that normal streaming completion still produces exactly one `response.completed` terminal event <!-- greptile_comment --> <h3>Greptile Summary</h3> Correctly fixes the duplicate terminal SSE event bug in streaming error responses by setting `closed=true` and calling `unsubscribe()` before writing the `response.failed` event, then properly closing the stream with `writeDone()` and `res.end()`. **Key Changes:** - Moved `closed = true` and `unsubscribe()` to the beginning of the catch block (before writing `response.failed`) - Removed the lifecycle `error` event emission that was triggering the duplicate `response.completed` event via the `onAgentEvent` listener - Added explicit stream cleanup with `writeDone(res)` and `res.end()` in the catch block - The finally block's `!closed` guard now correctly prevents duplicate lifecycle event emission after an error The fix ensures exactly one terminal SSE event per response stream, matching the OpenResponses specification. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - it fixes a clear spec violation with proper error handling - The fix correctly addresses the root cause by preventing the lifecycle error event that triggered the duplicate terminal event. The logic is sound with proper guards against race conditions (early return if `closed`, initialization of `unsubscribe` to noop). All existing tests pass, and the changes align with the existing error handling patterns in the codebase. - No files require special attention <sub>Last reviewed commit: 9751274</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs