Skip to content

Resume web CLI sessions on return instead of killing them on inactivity#408

Draft
matt423 wants to merge 2 commits into
mainfrom
dx-1379/web-cli-resume-on-return
Draft

Resume web CLI sessions on return instead of killing them on inactivity#408
matt423 wants to merge 2 commits into
mainfrom
dx-1379/web-cli-resume-on-return

Conversation

@matt423
Copy link
Copy Markdown
Member

@matt423 matt423 commented Jun 4, 2026

Summary

A backgrounded web CLI terminal was hard-closed after 5 minutes (close code 4002), which handleWebSocketClose treats as non-recoverable: the sessionId was purged and the user had to press Enter to start a brand-new session, losing their shell. Because visibility is onScreen && document.visibilityState === 'visible', simply switching browser tabs counts as "inactive" — so this surfaced as random "disconnects" when a tab was left open in the background (Ably DX-1379).

This PR makes the inactivity close a pause rather than a terminate, and refreshes credentials before reconnect handshakes so resumes don't fail on an expired signed config.

Changes

  • Resume on return — the inactivity close (4002 "inactivity-timeout") now preserves the sessionId. When the tab is foregrounded again the session is transparently resumed (the reconnect carries the sessionId). If the server rejects the resume (it reaped the detached session), we fall back to a fresh session rather than dead-ending on the manual reconnect prompt.
  • Configurable timeout — the previously-hardcoded 5-minute timeout is now an inactivityTimeoutMs prop (default unchanged). Also makes the pause/resume cycle quick to exercise locally.
  • Credential refresh — new optional async refreshCredentials() prop, awaited in the open handler just before sending the auth payload (primary + split-screen). Short-lived signed configs expire (the terminal server rejects configs older than a few minutes), so a resume — or any auto-reconnect more than a few minutes into a session — would otherwise be rejected with "Config expired". Embedders return a fresh config; when the prop is absent or errors, we fall back to the existing signedConfig/signature props, so behaviour is unchanged for consumers that don't supply it.

Tests

New AblyCliTerminal.inactivity.test.tsx drives the real visibility hook (IntersectionObserver + document.visibilityState) and asserts: pause preserves the session (no purge, closes 4002); return resumes carrying the sessionId; a rejected resume falls back to a fresh session; and resume uses freshly-refreshed credentials. Full package suite is green.

Notes for reviewers

  • The consumer side lives in the Ably website (CLIDrawer), which wires inactivityTimeoutMs and refreshCredentials. It depends on a published release of this package exposing the two new props.

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli-web-cli Ready Ready Preview, Comment Jun 4, 2026 5:02pm

Request Review

matt423 and others added 2 commits June 4, 2026 17:55
A backgrounded terminal was hard-closed after 5 minutes with close code
4002, which handleWebSocketClose treats as non-recoverable: the sessionId
was purged and the user had to press Enter to start a brand-new session,
losing their shell. Reported as random "disconnects" when a tab is left
open in the background.

Now the inactivity close preserves the session: the sessionId is kept and
the session is transparently resumed (reconnect carries the sessionId) as
soon as the tab is foregrounded again. If the server rejects the resume
(it reaped the detached session), we fall back to a fresh session rather
than dead-ending on the manual prompt.

The previously-hardcoded 5-minute timeout is now configurable via a new
inactivityTimeoutMs prop, which also makes the pause/resume cycle quick to
exercise locally.

DX-1379

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed configs are short-lived (the terminal server rejects configs older
than a few minutes). A resume-on-return — or any auto-reconnect more than a
few minutes into a session — reused the original signed config and was
rejected with "Config expired", forcing a noisy retry.

Add an optional async refreshCredentials() prop that the component awaits in
the open handler, just before sending the auth payload, for both the primary
and secondary (split-screen) terminals. Embedders return a fresh signed
config; when absent or on error we fall back to the signedConfig/signature
props, so behaviour is unchanged for consumers that don't supply it.

DX-1379

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant