Skip to content

fix(agentex): end SSE subscriptions on terminal task / vanished topic; stop deleting shared stream - #385

Draft
deepthi-rao-scale wants to merge 1 commit into
mainfrom
fix/agentex-sse-zombie-subscription-leak
Draft

fix(agentex): end SSE subscriptions on terminal task / vanished topic; stop deleting shared stream#385
deepthi-rao-scale wants to merge 1 commit into
mainfrom
fix/agentex-sse-zombie-subscription-leak

Conversation

@deepthi-rao-scale

@deepthi-rao-scale deepthi-rao-scale commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What was broken

When you open a task in the UI, the backend keeps a live stream open to push updates. That stream had no way to end itself — it only stopped if the browser disconnected.

So when a task finished, the backend just kept listening forever, holding one Redis connection each time. These pile up until the pool is empty, at which point the pod can't serve requests (and its health check fails, so it sits stuck until a manual restart).

A second bug: when one viewer left, the code deleted the task's shared event stream — yanking it out from under everyone else watching the same task.

The fix

  • End the stream when the task is done. The backend already gets a task_updated event when a task finishes, so the stream now closes as soon as it sees a terminal status.
  • Fallback for the rare cases the event can't cover (viewer connects after the task already finished, or the producer dies silently): a lightweight check on the existing keepalive interval closes the stream then too.
  • Stop deleting the shared stream on one viewer's exit — the Redis TTL already cleans it up on its own.

Tests

Two integration regression tests: the stream ends on its own once the task is terminal, and one viewer disconnecting no longer deletes the shared stream.

⚠️ Integration tests couldn't run locally (testcontainers Redis networking in my sandbox); relying on CI.

Related: the UI-side half of this leak is #383.

🤖 Generated with Claude Code

@deepthi-rao-scale
deepthi-rao-scale force-pushed the fix/agentex-sse-zombie-subscription-leak branch from 89b915c to 2846998 Compare July 29, 2026 18:16
…hared stream

Task event SSE subscriptions had no terminal condition. stream_task_events ran
a `while True` loop whose only exits were client disconnect (CancelledError) and
fatal errors. Once a task finished its producer stopped writing, but the reader
kept blocking on the topic forever — issuing an XREAD every couple of seconds
and pinning one connection from the shared per-process Redis pool. Because the
stream key carries a sliding TTL, a finished task's key eventually disappears
while readers keep blocking on it, turning each subscription into a permanent
zombie. Accumulated zombies exhaust the pool, which is shared with the readiness
probe, so /readyz fails while the dependency-free /healthz keeps returning 200 —
the pod goes Unready and is never restarted.

Termination:
- Primary is event-driven. Every terminal transition funnels through
  task_service.transition_status (and delete via update_task), which XADDs a
  task_updated event carrying the new status onto the task's own stream. The
  reader already delivers those events, so it returns as soon as it forwards one
  whose task is in a terminal status — no DB lookup and no ordering race, since
  the terminal event is the last message produced.
- Fallback runs on the keepalive-ping cadence for the cases the event path
  cannot see: a client that connects after the task already finished, a producer
  that dies without emitting a terminal event, or a task_updated with no task
  payload. It ends the stream when the task is terminal or when a topic that had
  existed is reclaimed. stream_ever_existed is seeded from the tail snapshot so a
  mid-flight subscriber correctly arms the vanished-topic check.

Shared stream:
- Drop the per-subscriber cleanup_stream in finally. The topic is keyed only by
  task id and shared by every viewer, so deleting it on one subscriber's exit
  tore the stream out from under the others. The sliding TTL already reclaims it.

Also adds stream_exists to the stream port + Redis adapter (EXISTS) for the
vanished-topic fallback, and two integration regression tests: the stream ends
on its own once the task is terminal, and a single subscriber disconnecting does
not delete the shared topic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@deepthi-rao-scale
deepthi-rao-scale force-pushed the fix/agentex-sse-zombie-subscription-leak branch from 2846998 to 2eed3af Compare July 29, 2026 18:35
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