Skip to content

Session state stale after device sleep/resume — heartbeat mismatch causes premature SSE disconnect #17769

Closed as not planned

Description

@BYK

Description

After a device resumes from sleep (closing laptop lid, phone lock screen, browser tab backgrounded for extended period), the web UI often shows stale session state — messages may be missing, the session appears stuck, or the UI shows "Unable to retrieve session". The connection does recover eventually but the state is often inconsistent.

Root cause: server/client heartbeat mismatch

The server sends SSE heartbeats every 30 seconds (server.ts:692), but the client expects a heartbeat within 15 seconds (global-sdk.tsx:108 HEARTBEAT_TIMEOUT_MS = 15_000). During normal operation this works because regular events (message updates, status changes) reset the client timer. But during idle periods with no activity, the client will timeout and abort the SSE connection between heartbeats — causing unnecessary reconnection churn even without a sleep/resume event.

Reconnection after sleep/resume:

  1. Device sleeps → SSE connection goes stale, TCP socket may be closed by OS
  2. Device wakes → visibilitychange fires → client checks lastEventAt
  3. Since lastEventAt is from before sleep, Date.now() - lastEventAt >> 15s → attempt?.abort() fires
  4. SSE reconnect loop starts (250ms delay) → new connection → server sends server.connected
  5. Client re-bootstraps all directories (sessions, messages) — this part works
  6. But global state (config, providers, auth) is NOT re-fetched on reconnect — only directory-level data is refreshed via queue.push(directory)
  7. Any session that was mid-stream (assistant generating a response) when sleep happened loses that streaming state — the partial message is frozen and no further deltas arrive

Additional issue: SDK SSE client has no max retry attempts by default

The SDK's createSseClient (serverSentEvents.gen.ts:225) only limits retries if sseMaxRetryAttempts is explicitly set. The global-sdk wrapper does not set this parameter — it uses its own reconnect loop (line 124-191) that wraps the SDK stream. However, if the SDK's internal retry loop gets stuck (e.g. server returns 502 repeatedly), the exponential backoff can reach 30s between attempts while the outer loop also waits 250ms — leading to confusing double-retry behavior.

Steps to reproduce

  1. Open opencode serve web UI on a phone or laptop
  2. Start a session and send a message
  3. While the assistant is responding, lock the device / close the lid / switch tabs for 30+ seconds
  4. Resume the device / reopen the tab
  5. Observe: the assistant response may be frozen, or the session shows stale data

OpenCode version

1.2.26

Operating System

Any (macOS, iOS Safari, Android Chrome, Linux)

Terminal

N/A (web UI via opencode serve)

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 16, 2026
  2. anas-asghar4831 commented on Apr 4, 2026

    @anas-asghar4831

    Hitting this constantly on mobile. My setup:

    • OpenCode hosted on a remote VM, exposed via public URL
    • Accessing through Chrome on Android, also installed as PWA (Add to Home Screen)
    • Model: claude-opus-4-6 + max variant

    Browser tab: switching to any other app or locking the phone kills the SSE stream. When I come back, the conversation is frozen — I have to pull-to-refresh or manually reload the page to see the completed response.

    PWA: even worse — switching away from the PWA means I have to force-close and reopen it entirely. There's no pull-to-refresh in PWA mode, so the only recovery is killing the app.

    This is the single biggest usability pain point on mobile. A reconnect that automatically re-fetches the full session state (like #13947's fix suggested — treating server.connected as global.disposed to trigger a full refresh) would solve it.

  3. IceWreck commented on Apr 12, 2026

    @IceWreck
    Contributor

    Also facing this all the time.

  4. assigned and unassigned on Apr 26, 2026
  5. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. vitalimi commented on May 13, 2026

    @vitalimi

    Have same issue:
    Opencode serve on VM, using web ui via clodflare/zrok/pinggy.
    After sending message i have to manually refresh page to see models answers. After 15 seconds event error

  7. corvofeng commented on May 21, 2026

    @corvofeng

    Would it be possible to add a refresh button or a better tombstone mechanism? On the web version I can at least manually refresh the page, but as a PWA app I'm stuck staring at a frozen screen with no way to recover.

  8. github-actions commented on Jul 21, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions