Repository navigation
Remote 0.2.51: failed 45-minute token refresh has no near-term retry, leading to expired Realtime JWT #747
Description
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:
- Make manual refresh single-flight so the 45-minute tick and a retry cannot overlap.
- On
AuthRetryableFetchError(plus retryable server/rate-limit cases), schedule bounded near-term retry/backoff, e.g.5s -> 15s -> 60s -> 5m cap. - On successful refresh, clear retry state and continue the normal 45-minute cadence.
- 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
davidqyc commentedon Sep 22, 2026
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:
The later canary never reached meaningful validation of the proposed retry patch, so it neither accepts nor rejects that patch.