Skip to content

fix(session-overview): done sessions never transition to idle#384

Open
dimakis wants to merge 2 commits into
mainfrom
fix/session-idle-transition
Open

fix(session-overview): done sessions never transition to idle#384
dimakis wants to merge 2 commits into
mainfrom
fix/session-idle-transition

Conversation

@dimakis

@dimakis dimakis commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Idle transition timer: touch() now schedules a broadcast at DONE_TIMEOUT_MS (5 min) so the done→idle transition fires proactively, even when no other events occur
  • Persistent session timeout: persistentToActivity() now applies the same elapsed-time check instead of hardcoding state: 'done' — old attention sessions transition to idle and disappear from the active list

Root cause

Sessions were stuck as "done" in the Active Sessions list indefinitely because:

  1. State derivation was purely on-demand — no timer existed to trigger re-evaluation after 5 minutes of inactivity
  2. Sessions from getAttentionSessions() (assistant spoke last) were always returned as done regardless of age

Test plan

  • Existing tests pass (38/38)
  • New test: idle transition timer broadcasts after DONE_TIMEOUT_MS
  • New test: subsequent touch resets the idle timer
  • New test: persistent sessions transition to idle after timeout
  • Updated test: recent persistent sessions still show as done

🤖 Generated with Claude Code

dimakis and others added 2 commits June 12, 2026 22:32
…reattach

EventSource auto-reconnect reuses the original URL, which never included
the ?sessions= query param. This meant the server never ran handleReconnect
on auto-reconnect — no watch, no reattach, no event replay. Users had to
send multiple messages before the session would respond.

Move reconnect from URL query param to explicit POST in the welcome handler.
This fires on every reconnect (auto or explicit), ensuring the server always
runs the full reconnect flow: watch + reattach + event replay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two bugs caused sessions to stay visible in the Active Sessions list
indefinitely instead of disappearing after 5 minutes:

1. No proactive idle timer — the done→idle transition was only checked
   during on-demand state derivation. If no events occurred after a
   session finished, the server never re-evaluated or broadcast the
   state change. Fixed by scheduling a broadcast at DONE_TIMEOUT_MS
   after each touch().

2. Persistent attention sessions hardcoded to "done" — sessions from
   EventStore's getAttentionSessions() always returned state:'done'
   regardless of age. Fixed by applying the same elapsed-time check
   as live sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dimakis

dimakis commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

Centaur Review

Found 4 issue(s) (1 warning).

packages/client/src/sse-connection.ts

Solid fix: persistent sessions correctly transition done→idle, and SSE reconnect moves from fragile URL params to a reliable POST. One minor race (unawaited POST before flush) and a timer-precision tradeoff worth noting, but nothing blocking.

  • 🟡 bugs (L224): The reconnect POST (doPost) is fire-and-forget — not awaited before flushPendingSends() on line 234. If the client has queued sends, they may arrive at the server before the reconnect POST completes, meaning the server hasn't yet set up watches or reattached sessions. In practice this is likely benign (sends route independently of watch setup, and the server replays missed events), but under high latency the ordering gap widens. Consider awaiting the POST or moving flushPendingSends into the POST's .then() callback. [fixable]

server/session-overview.ts

Solid fix: persistent sessions correctly transition done→idle, and SSE reconnect moves from fragile URL params to a reliable POST. One minor race (unawaited POST before flush) and a timer-precision tradeoff worth noting, but nothing blocking.

  • 🔵 unsafe_assumptions (L110): The idle-transition timer is a single shared timer that resets on every touch() call for any session. If session A finishes at t=0 and session B finishes at t=4min, the timer resets to t=9min. Session A's idle broadcast is delayed 4 minutes beyond its actual DONE_TIMEOUT_MS threshold. This is safe because compute() evaluates all sessions at broadcast time (so A will correctly show as idle when the broadcast fires), but the UX lateness scales with inter-session activity gaps. [fixable]
  • 🔵 style (L218): The 6-line block comment on the reconnect POST in doConnect() (lines 218–223) largely restates the 4-line comment already present at the const url declaration (lines 198–201). The duplication adds noise; a single-line reference like // POST reconnect — see comment above would suffice. [fixable]

packages/client/src/__tests__/sse-connection.test.ts

Solid fix: persistent sessions correctly transition done→idle, and SSE reconnect moves from fragile URL params to a reliable POST. One minor race (unawaited POST before flush) and a timer-precision tradeoff worth noting, but nothing blocking.

  • 🔵 missing_tests: No test verifies behavior when the reconnect POST fails (e.g., network error or non-ok response). The doPost catch block silently swallows errors, which is intentional — a test documenting that the connection stays healthy after a failed reconnect POST would guard this invariant. [fixable]

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