Skip to content

fix(cli): SSE streaming can no longer hang or silently truncate (PR C — hardening)#21

Merged
Harsh-2002 merged 1 commit into
mainfrom
dev
Jun 14, 2026
Merged

fix(cli): SSE streaming can no longer hang or silently truncate (PR C — hardening)#21
Harsh-2002 merged 1 commit into
mainfrom
dev

Conversation

@Harsh-2002

Copy link
Copy Markdown
Owner

PR C — SSE / chat hardening

Third of four back-to-back CLI gap-closure PRs. Closes the two residual holes found during the AI-chat hang validation: the CLI relied entirely on the server never going silent, and accepted a mid-stream connection drop as a successful (truncated) turn.

Changes

  • Idle deadlineclient.Request gains IdleTimeout; when set, Send wraps the response body in an idleReadCloser that cancels the request context if no bytes arrive for the window (reset on every byte → a true idle timeout, never a total-duration cap). DefaultStreamIdleTimeout = 45s (3× the server's 15s heartbeat). Applied to every streaming caller: chat (postChat + approve/reject), invoke --stream, and the shared streamSSE (logs/activity/deploy/deployments-logs follow) — which is now routed through client.Send instead of a bespoke timeout-free http.Client, collapsing to one hardened streaming path.
  • Premature-EOF as error (per-caller, not in consumeSSE — follow-mode and raw --stream still end cleanly on EOF): chat.drive errors when the stream ends without a terminal frame (done/awaiting_approval/error) instead of accepting a truncated turn as success; followDeploymentLogs gets the same terminal guard watchBuild already had. Guarded on err==nil so it never masks a Ctrl-C (context.Canceled) or a real transport error.

Tests

TestDrivePrematureEOF (EOF without a terminal frame → error), TestClientIdleTimeout (silent stream cancelled within the idle window); existing TestDriveSSE (heartbeat + clean done) stays green. go vet ./... + go test ./... green.

Live-verified on the dev instance (:3000)

Happy-path chat still streams (pong, exit 0); deployments logs --follow on an already-completed deployment replays the terminal frame and exits 0 (no false-positive from the new guard); activity --follow + logs --follow stream through the rewritten streamSSE without a spurious EOF error.

Closes the two residual hardening holes from the AI-chat hang validation —
the CLI relied entirely on the server never going silent.

- Idle deadline: client.Request gains IdleTimeout; when set, Send wraps the
  response body so the request context is cancelled if no bytes arrive for
  the window (reset on every byte, so it's an idle timeout, not a total cap).
  DefaultStreamIdleTimeout = 45s (3× the server's 15s heartbeat). Applied to
  every streaming caller: chat (postChat + approve/reject), invoke --stream,
  and the shared streamSSE (logs/activity/deploy/deployments-logs follow),
  which is now routed through client.Send instead of a bespoke timeout-free
  http.Client — one hardened streaming path.
- Premature-EOF as error (per-caller, not in consumeSSE so follow-mode and
  raw --stream still end cleanly on EOF): chat.drive errors when the stream
  ends without a terminal frame (done/awaiting_approval/error) instead of
  accepting a truncated turn as success; followDeploymentLogs gets the same
  terminal guard watchBuild already had. Guarded on err==nil so it never
  masks a Ctrl-C or a real transport error.

Tests: TestDrivePrematureEOF (EOF w/o terminal frame -> error),
TestClientIdleTimeout (silent stream cancelled within the window); existing
TestDriveSSE (heartbeat + clean done) stays green.
@Harsh-2002 Harsh-2002 merged commit d0edc5f into main Jun 14, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant