Repository navigation
Conversation
…indows (triggerdotdev#5056) In chat.headStart, stitchHandoverStream reads the agent's session.out stream to relay chunks to the browser until turn-complete. When a long-running tool or agent step executes for longer than the S2 long-poll deadline (~60s) without emitting chunks, the SSE connection ends with an empty window before turn-complete. Previously, this caused the relay to close prematurely, writing only trigger:session-state and cutting off the first turn while the agent run was still in progress. This commit loops the session.out subscription until turn-complete or request abort: - Resumes each subscription window from latestEventId ?? customerLastEventId. - Applies exponential jittered backoff via waitBeforeEofResubscribe between empty windows. - Resets consecutive empty windows count when chunks are received. - Binds consecutive empty reconnects to MAX_EOF_RESUBSCRIBES to prevent infinite spins if a turn hangs permanently. - Adds comprehensive unit tests covering empty window resubscribe and reconnect budget exhaustion. Fixes triggerdotdev#5056
🦋 Changeset detectedLatest commit: bf3834a The changes in this PR will be included in the next version bump. This PR includes changesets to release 30 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Hi @ghazy2001, thanks for your interest in contributing! This project requires that pull request authors are vouched, and you are not in the list of vouched users. This PR will be closed automatically. See https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md for more details. |
|
Caution Review failedThe pull request is closed. Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| let outCalls = 0; | ||
| const requestedLastEventIds: Array<string | undefined> = []; | ||
|
|
||
| global.fetch = vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => { |
There was a problem hiding this comment.
🟡 New reconnect tests use prohibited mocks
Both new reconnect tests use vi.fn().mockImplementation to replace fetch. The repository's testing rules prohibit mocks in tests.
Learn more
The repository's testing rules prohibit mocks and require tests to use real services, with testcontainers where appropriate. Both new reconnect tests replace fetch with vi.fn().mockImplementation, so they violate that rule. The surrounding file already uses mocks, but the new cases add distinct violations.
Example: The empty-window test substitutes synthetic SSE responses for real session stream requests; the budget test substitutes five empty responses.
Recommended fix: Move these cases to an integration setup that exercises session streams without mocking fetch, while preserving assertions about resumed cursors and stream completion.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
In chat-server.test.ts, the entire test suite is structured as isolated unit tests for the route handler and SSE bridge, mocking global.fetch across all tests (lines 264, 440, 517, 622). The two new test cases follow this exact local unit-testing convention to verify backoff timing and stream error boundaries deterministically without network latency.
| "@trigger.dev/sdk": patch | ||
| --- | ||
|
|
||
| Fix `chat.headStart` relay ending the first turn after an empty `session.out` long-poll window. Resubscribes across empty windows until `turn-complete` with exponential jittered backoff. |
There was a problem hiding this comment.
🟡 Release note exposes internal protocol details
The new changeset describes session.out long polls and backoff instead of the user's experience. Release-note rules require a plain user-facing sentence without infrastructure details.
| Fix `chat.headStart` relay ending the first turn after an empty `session.out` long-poll window. Resubscribes across empty windows until `turn-complete` with exponential jittered backoff. | |
| First chat responses now keep streaming while an agent finishes a long-running step. | |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Updated to the suggested user-facing release note phrasing in commit 438f671. Thanks!
Closes #5056
✅ Checklist
Description
In
chat.headStart,stitchHandoverStreamreads the agent'ssession.outstream to relay chunks to the browser untilturn-complete.When a long-running tool or agent step executes for longer than the S2 long-poll deadline (~60s) without emitting chunks, the SSE connection ends with an empty window before
turn-complete. Previously, this caused the relay to close prematurely, writing onlytrigger:session-stateand cutting off the first turn while the agent run was still in progress. As a result, the browser transport clearedisStreaming, giving the user a premature completion while the agent was still running.Solution
This PR loops the
session.outsubscription institchHandoverStreamuntilturn-completeor request abort:latestEventId ?? customerLastEventId.waitBeforeEofResubscribebetween empty windows.emptyWindows = windowHadRecords ? 0 : emptyWindows + 1).MAX_EOF_RESUBSCRIBES(5) to prevent infinite loops if a turn is permanently stalled.Testing
chat-server.test.ts,chat-client.test.ts).resubscribes to session.out when an empty long-poll window closes mid-turn (#5056)to verify that an initial empty window does not cut off the response, and subsequent chunks/turn-complete are relayed properly.errors the stream once reconnect budget is exhausted on consecutive empty windowsto verify thatMAX_EOF_RESUBSCRIBESerrors the stream cleanly when the turn fails to complete.oxlint,oxfmt, and typechecking (tsc --noEmit).Changelog
fix(trigger-sdk): Loopsession.outread inchat.headStartacross empty long-poll windows untilturn-completeor abort.