fix(agent): keep idle event streams alive#2032
Merged
Conversation
Contributor
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
packages/agent/src/server/agent-server.ts:344-366
`sseController` is created before `enqueueSseFrame` is declared, yet `sseController.send` directly calls `enqueueSseFrame`. This works at runtime because the closure captures the binding rather than the value and `send` is never invoked before the `const` initialises, but it creates a fragile forward-reference: any future refactor that causes `sseController.send` to be called synchronously before `enqueueSseFrame` is declared (e.g. an eager callback inside `initializeSession`) would produce a TDZ ReferenceError. Declaring `enqueueSseFrame` first removes the dependency entirely.
```suggestion
const encoder = new TextEncoder();
const enqueueSseFrame = (frame: string): void => {
try {
controller.enqueue(encoder.encode(frame));
} catch {
clearKeepalive();
this.detachSseController(sseController);
}
};
const sseController: SseController = {
send: (data: unknown) => {
enqueueSseFrame(`data: ${JSON.stringify(data)}\n\n`);
},
close: () => {
try {
clearKeepalive();
controller.close();
} catch {
this.detachSseController(sseController);
}
},
};
```
### Issue 2 of 2
packages/agent/src/server/agent-server.test.ts:284-291
The test hard-codes `25_000` to identify the keepalive interval, duplicating the value of `SSE_KEEPALIVE_INTERVAL_MS` from production code (OnceAndOnlyOnce). If the constant is ever changed, this test will silently stop exercising the real keepalive logic without a compile-time error. Exporting the constant and importing it in the test removes the duplication.
```suggestion
.mockImplementation(
(callback: (_: undefined) => void, timeout?: number) => {
if (timeout === SSE_KEEPALIVE_INTERVAL_MS) {
keepaliveCallback.current = () => callback(undefined);
}
return setTimeout(() => undefined, 60_000);
},
);
```
Reviews (1): Last reviewed commit: "fix(agent): keep idle event streams aliv..." | Re-trigger Greptile |
skoob13
approved these changes
May 5, 2026
| this.replayPendingEvents(); | ||
| } | ||
| keepaliveInterval = setInterval(() => { | ||
| enqueueSseFrame(": keepalive\n\n"); |
Contributor
There was a problem hiding this comment.
I didn't know that it's conventional to omit the message prefix🤯
Contributor
Author
There was a problem hiding this comment.
ahaha yeah, SSE have specific rules there in the parser, here we basically want to send bytes over the idle socket to avoid read timeouts, if we were to use the other data: convention, that would create a real SSE data message instead
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.
Problem
cloud task event streams can sit idle while the sandbox is still healthy. The backend reader has an inactivity timeout, so idle
/eventssockets can disconnect and surface asCloud stream disconnectedeven though retry immediately reconnectsChanges
/eventsendpoint every 25 seconds, keeping the socket active at the transport layer and are ignored by SSE event parsers, so no task event or workflow heartbeat is created