You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If the machine hosting a share loses its network for more than fifteen minutes — a laptop sleeping over lunch, a WiFi switch, a VPN reconnect — the relay permanently deletes the session, and the CLI can never get it back. The wrapped process keeps running the whole time, completely unaware. Every link you handed out is dead, and the only way to notice is to run shell list and spot that the relay column now says expired.
The reason is that a disconnect collapses the session's remaining life from twelve hours to fifteen minutes. When the host socket closes, handleSocketEnd rewrites the expiry:
with DISCONNECTED_GRACE_MS = 15 * 60 * 1000 (worker/index.ts:42). A durable alarm then deletes the session when that window elapses.
The fifteen-minute grace is a defensible design choice on its own. What makes this a bug is that there is no way back. The host's relay layer retries forever with a ten-second backoff cap (internal/relay/relay.go), but it only ever reconnects to a session ID — it never re-registers. Once the relay has deleted that ID, the CLI is dialling something that no longer exists, indefinitely, while holding a live PTY and valid host credentials. Nothing surfaces the failure to the person who created the share.
Reproduction on production
Verified against the live relay, not just locally. A TLS-terminating proxy sat between the CLI and shell.online so the host's connection could be cut on demand while the relay stayed up — the same shape as a sleeping laptop. The session itself was a genuine production session, confirmed by querying https://shell.online/api/sessions/<id> directly rather than through the proxy.
Persistent sessions get PERSISTENT_TTL_MS on disconnect instead of the grace window, and there is a /api/sessions/resume path for re-registering. Default sessions have neither.
What was ruled out
SIGSTOP on the host does not reproduce it. Freezing the process leaves the TCP socket open, so the relay still reports connected — confirmed for four minutes on production. Only an actual connection break triggers it.
Not the 12-hour TTL. Far too long to explain links dying inside a working day.
Not the client giving up.internal/relay/relay.go never stops retrying; it simply has nothing to reconnect to.
Impact
This is invisible and unrecoverable. The owner gets no notification, the process keeps consuming resources, and recipients see "Session ended. This sharing link no longer exists." Anyone who shares a link and then closes their laptop for a meeting comes back to a dead link and a running process. Because the trigger is ordinary laptop behaviour rather than anything the user did to the session, it reads as links going offline at random.
Suggested fixes
Let the host re-register after its session has been reaped. The CLI still holds the host credential; an equivalent of /api/sessions/resume for non-persistent sessions would make the existing infinite reconnect loop actually able to succeed.
Failing that, have the CLI detect a 404/410 on reconnect, stop looping, and tell the owner the share is gone — ideally offering to reissue.
Consider extending or making the grace window configurable; fifteen minutes is shorter than a lunch break.
shell 0.7.3 (latest; matches release.json and the GitHub latest release), macOS 26.2 arm64, against the production shell.online relay. Worker behaviour cross-checked in the v0.7.3 source tree.
If the machine hosting a share loses its network for more than fifteen minutes — a laptop sleeping over lunch, a WiFi switch, a VPN reconnect — the relay permanently deletes the session, and the CLI can never get it back. The wrapped process keeps running the whole time, completely unaware. Every link you handed out is dead, and the only way to notice is to run
shell listand spot that the relay column now saysexpired.The reason is that a disconnect collapses the session's remaining life from twelve hours to fifteen minutes. When the host socket closes,
handleSocketEndrewrites the expiry:with
DISCONNECTED_GRACE_MS = 15 * 60 * 1000(worker/index.ts:42). A durable alarm then deletes the session when that window elapses.The fifteen-minute grace is a defensible design choice on its own. What makes this a bug is that there is no way back. The host's relay layer retries forever with a ten-second backoff cap (
internal/relay/relay.go), but it only ever reconnects to a session ID — it never re-registers. Once the relay has deleted that ID, the CLI is dialling something that no longer exists, indefinitely, while holding a live PTY and valid host credentials. Nothing surfaces the failure to the person who created the share.Reproduction on production
Verified against the live relay, not just locally. A TLS-terminating proxy sat between the CLI and
shell.onlineso the host's connection could be cut on demand while the relay stayed up — the same shape as a sleeping laptop. The session itself was a genuine production session, confirmed by queryinghttps://shell.online/api/sessions/<id>directly rather than through the proxy.Host side, throughout and afterwards:
The process is still alive at 22 minutes. The link is gone for good.
--persistentis immune, which confirms the mechanismThe same outage against a persistent session (run locally with the grace window shortened to 20s so the boundary is easy to cross):
Persistent sessions get
PERSISTENT_TTL_MSon disconnect instead of the grace window, and there is a/api/sessions/resumepath for re-registering. Default sessions have neither.What was ruled out
SIGSTOPon the host does not reproduce it. Freezing the process leaves the TCP socket open, so the relay still reportsconnected— confirmed for four minutes on production. Only an actual connection break triggers it.internal/relay/relay.gonever stops retrying; it simply has nothing to reconnect to.Impact
This is invisible and unrecoverable. The owner gets no notification, the process keeps consuming resources, and recipients see "Session ended. This sharing link no longer exists." Anyone who shares a link and then closes their laptop for a meeting comes back to a dead link and a running process. Because the trigger is ordinary laptop behaviour rather than anything the user did to the session, it reads as links going offline at random.
Suggested fixes
/api/sessions/resumefor non-persistent sessions would make the existing infinite reconnect loop actually able to succeed.shell listmisreporting an expired session as running. That reporting is now correct — this issue is about the session being destroyed and unrecoverable in the first place.Environment
shell 0.7.3(latest; matchesrelease.jsonand the GitHub latest release), macOS 26.2 arm64, against the productionshell.onlinerelay. Worker behaviour cross-checked in thev0.7.3source tree.