Skip to content

Remote 0.2.51: failed 45-minute token refresh has no near-term retry, leading to expired Realtime JWT #747

Open

Description

@davidqyc

Summary

On Desktop Commander Remote 0.2.51, a transient failure of the manual Supabase session refresh can leave the device running on the old access JWT until it expires, because the next refresh attempt is still the next fixed 45-minute interval.

In a live macOS Remote session I observed this sequence in the same production stderr stream:

[DEBUG] Manual token refresh failed: fetch failed
... repeated transport failure / heartbeat timeout ...
[DEBUG] Manual token refresh failed: Invalid Refresh Token: Refresh Token Not Found
Channel error: InvalidJWTToken: Token has expired 6 seconds ago
Channel error: InvalidJWTToken: Token has expired 11 seconds ago
Channel error: InvalidJWTToken: Token has expired 17 seconds ago
...

During the incident the device record could still show a valid auth token while Remote presence/execution became unavailable. This is distinct from #710's persisted-refresh-token-on-restart bug.

Environment

  • Desktop Commander Remote: 0.2.51
  • macOS Apple Silicon
  • Node.js: 24.16.0
  • transport: broadcast_v1
  • one launchd-owned Remote agent
  • ChatGPT Remote MCP connector

No tokens, device IDs, account IDs, or private paths are included.

Source-level cause

src/remote-device/remote-channel.ts in v0.2.51 currently does:

createClient(... auth: { autoRefreshToken: false })

startTokenRefresh():
  setInterval(refreshTokenNow, 45 minutes)

refreshTokenNow():
  await client.auth.refreshSession()
  if error:
    log/capture only
  else:
    success

So a refresh attempt that lands during a transient network outage is not retried soon. The old JWT keeps aging until either another path repairs the session or the next 45-minute tick occurs.

TOKEN_REFRESHED does correctly call realtime.setAuth(newSession.access_token) and PR #710 correctly persists rotated refresh tokens. The missing piece is retry / terminal-error routing when refreshSession() itself fails.

Current upstream main still has the same fixed-cadence/no-retry refreshTokenNow() shape.

Why this matters

This creates a liveness gap:

scheduled refresh
-> transient fetch/transport failure
-> no near-term retry
-> current access JWT expires
-> Realtime reconnects begin failing with InvalidJWTToken
-> device can become offline/unusable even though the process remains alive

It also handles a terminal refresh-token rejection poorly: refresh_token_not_found / refresh_token_already_used are only logged by refreshTokenNow() instead of immediately entering the existing one-shot handleSignedOut() restore/offline path.

Proposed behavior

Minimal change inside RemoteChannel, without a new daemon/control plane:

  1. Make manual refresh single-flight so the 45-minute tick and a retry cannot overlap.
  2. On AuthRetryableFetchError (plus retryable server/rate-limit cases), schedule bounded near-term retry/backoff, e.g. 5s -> 15s -> 60s -> 5m cap.
  3. On successful refresh, clear retry state and continue the normal 45-minute cadence.
  4. On explicit terminal auth codes such as:
refresh_token_not_found
refresh_token_already_used
session_not_found
session_expired

do not hammer the bad token; route immediately through the existing handleSignedOut() restore/session-loss path.
5. stopTokenRefresh() / shutdown / session-loss must clear any pending retry timer.

Supabase auth-js already exposes isAuthRetryableFetchError and isAuthApiError, so this does not need string matching.

Local isolated proof

I built the above shape against the exact v0.2.51 tag in an isolated worktree. Production Remote was not patched.

Validation:

npm run build                                      PASS
new retry regression:
  transient refresh schedules 5s retry             PASS
  later success clears retry state                  PASS
  overlapping refresh callers are single-flight    PASS
  refresh_token_not_found -> session-loss path     PASS
  stopTokenRefresh clears pending retry             PASS

existing related regressions:
  test-remote-token-rotation-persisted.js           PASS
  test-remote-channel-signed-out.js                 PASS
  test-remote-clock-skew-before-session.js          PASS
  test-remote-channel-reconnect.js                  PASS
  test-remote-transport.js                          PASS

The source-only local candidate is based on exact v0.2.51 commit 092ce0b841e86455f12e41f4dc36399a7522ecb5; it is not installed in production.

Request

Would you consider adding bounded retry + terminal-error routing to the manual token refresh path? The current 45-minute fixed cadence appears to leave a large recovery hole exactly when the refresh attempt coincides with a transient transport outage.

Activity

  1. davidqyc commented on Sep 22, 2026

    @davidqyc
    Author

    Scope clarification: a later production canary outage on this Mac was traced to a local WorkstationOps launchd-helper orchestration bug (a supposed one-shot helper was actually KeepAlive and repeatedly restarted the canonical Remote DC). That incident is unrelated to this issue and should not be counted as evidence for #747.

    #747 remains based only on the earlier, independently observed refresh failure plus the 0.2.51 source behavior documented in the issue:

    • manual refresh runs on the fixed ~45-minute cadence;
    • a retryable refresh failure is logged but has no near-term retry;
    • terminal refresh-token errors are not immediately routed through the existing session-loss recovery path.

    The later canary never reached meaningful validation of the proposed retry patch, so it neither accepts nor rejects that patch.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions