Remote device cannot survive a restart: persisted refresh token is never updated after rotation (v0.2.50)

Author: mypacingCreated Sep 11, 2026Updated Sep 18, 2026

We run desktop-commander remote as a systemd service on a headless Ubuntu server. It works well, but it cannot recover from a restart without a human, and I believe the cause is a small bug rather than a configuration mistake on our side.

Environment

  • @wonderwhy-er/desktop-commander 0.2.50, installed globally via npm
  • Node v22.22.1, Ubuntu 26.04.1 LTS, headless (no browser, no desktop)
  • started by systemd, Restart=always

What happened

An unattended security upgrade replaced libc6 and openssh; needrestart then restarted every service linked against them, including ours. The restart itself was clean. What followed was not:

 Found persisted session for device 5c996164-…
[DEBUG] Failed to set session: Invalid Refresh Token: Already Used
   - ⚠️ Persisted session invalid: Invalid Refresh Token: Already Used
 Authenticating with Remote MCP server...
 Starting device authorization flow...
   1. Open this URL in your browser:
      https://mcp.desktopcommander.app/device/verify
   2. Enter this code when prompted:
      7GAR-7YN7
   Code expires in 15 minutes.
 - ❌ Device startup failed: Device code has expired
 Shutting down device...
systemd[1]: desktop-commander.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: desktop-commander.service: Scheduled restart job, restart counter is at 15.

systemd restarted it, a new code was printed, nobody was there to enter it, and so on — 15 cycles over six hours. Throughout, systemctl is-active reported active (running) and the process was alive, so nothing looked wrong from the outside. On an unattended server this is a silent total loss of remote access.

Root cause

In dist/remote-device/:

  • device.js:158savePersistedConfig() is called from exactly one place, inside start(), right after authentication.
  • The access token is valid for 60 minutes (verified by decoding iat/exp of the JWT stored in device.json). On each renewal GoTrue also rotates the refresh token.
  • remote-channel.js:238 — the TOKEN_REFRESHED handler re-authorizes the realtime socket and stores the new pair in this.lastKnownSession, i.e. in memory only. Nothing writes it back to device.json.

So one hour after login the file on disk holds a consumed refresh token, and every restart from that point on ends in Invalid Refresh Token: Already Used. We confirmed that the mtime of device.json never changed between login and the failure.

The comment at device.js:32-35 about orphaned refresh-token families and GoTrue's reuse detection suggests the rotation behaviour is known — it just seems the persisted copy was not included.

Suggested fix

Persist the session whenever it is rotated: either call savePersistedConfig() from the TOKEN_REFRESHED branch, or run it on a timer.

One caution for the timer variant: savePersistedConfig() writes session: null when getSession() returns nothing, so an unguarded periodic call can erase valid credentials instead of preserving them. Our patch only saves when a session with a refresh_token is actually present.

Workaround we are running

We patched device.js to re-save the session every five minutes, guarded as above. Verified afterwards: device.json is rewritten on schedule (start 11:27:37, next write 11:32:37), and a service restart now reconnects with Found persisted sessionDevice ready, no code required.

One smaller thing

give_feedback_to_desktop_commander opens a browser form on the device. On a headless server that cannot work, so there is no in-product way to report this from the machine where it occurs.

Happy to supply the full journal or to test a fix.

Source: wonderwhy-er/DesktopCommanderMCP