Remote device cannot survive a restart: persisted refresh token is never updated after rotation (v0.2.50)
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-commander0.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:158—savePersistedConfig()is called from exactly one place, insidestart(), right after authentication.- The access token is valid for 60 minutes (verified by decoding
iat/expof the JWT stored indevice.json). On each renewal GoTrue also rotates the refresh token. remote-channel.js:238— theTOKEN_REFRESHEDhandler re-authorizes the realtime socket and stores the new pair inthis.lastKnownSession, i.e. in memory only. Nothing writes it back todevice.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 session → Device 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