When you start a share you can give it a deadline with --auto-close, and the reference invites you to think in long spans — the accepted units include days, weeks, months and years. But an ordinary share only lives twelve hours. The relay stamps every non-persistent session with a twelve-hour expiry at creation and deletes it when that moment arrives. If you ask for anything longer, nothing refuses you and nothing warns you: the command succeeds, the share opens normally, and the CLI reports the deadline you asked for as though it were real.
So shell --auto-close 7d <long job> tells you the session closes in seven days, and shell list keeps agreeing (CLOSES: in 6d23h) every time you look. Both numbers are fiction. Twelve hours later the relay expires the session and anyone opening the link you handed out gets "Session ended. This sharing link no longer exists."
The cause is that two different clocks are reported as one. The CLI computes your requested deadline locally and prints it as closes_at; the server independently sets expires_at from its own fixed lifetime. Both values travel back in the same payload, side by side, and nothing in the CLI ever compares them.
Reproduction
shell 0.7.3, default (non-persistent) session, three consecutive runs:
$ shell --json --auto-close 7d sleep 20
closes_at reported: +6d23h
expires_at actual : +0d11h <- the link dies here
$ shell list
ID UPTIME RELAY CLOSES ACCESS COMMAND
AN9yfku0Ww 3s online in 6d23h interactive+e2ee sleep 30
Measured across a range, on a session created at 09:43Z:
--auto-close |
closes_at reported |
expires_at actual |
|
| 1h |
2026-09-03T12:43+02:00 |
2026-09-03T21:43Z |
ok |
| 9h |
2026-09-03T20:44+02:00 |
2026-09-03T21:44Z |
ok |
| 24h |
2026-09-04T11:44+02:00 |
2026-09-03T21:44Z |
misreported |
| 7d |
2026-09-10T11:44+02:00 |
2026-09-03T21:44Z |
misreported |
| 30d |
2026-10-03T11:44+02:00 |
2026-09-03T21:44Z |
misreported |
Root cause
SESSION_TTL_MS = 12 * 60 * 60 * 1000 (worker/index.ts:41), applied at creation as expiresAt = createdAt + (persistent ? PERSISTENT_TTL_MS : SESSION_TTL_MS) (worker/index.ts:600) and enforced at worker/index.ts:739-741, which calls this.expire() and returns {exists:false} with 404.
On the CLI side the requested deadline is parsed at cmd/shell/main.go:69 and emitted as closes_at at cmd/shell/main.go:216. It is never compared against session.ExpiresAt, which arrives in the same struct — there is no reconciliation anywhere in cmd/shell.
Scope
--persistent raises the server lifetime to thirty days (PERSISTENT_TTL_MS, worker/index.ts:43), and a seven-day deadline there is genuinely honoured — confirmed, expires_at lands at +29d23h, 2/2 runs. Pushing past thirty days on a persistent session reproduces the same misreporting. The general rule: any deadline exceeding the session's actual lifetime is accepted, echoed back, and silently truncated.
Impact
The twelve-hour cap is reasonable; the false confidence built on top of it is the problem. Without closes_at a user would verify before promising a colleague a week of access. Instead the tool states a specific date, repeats it in shell list on every check, and gives no hint at any point that the figure is unachievable. The mistake surfaces hours later in someone else's browser as a dead link, with nothing in front of the owner connecting that failure to the flag that caused it.
Suggested fixes
Any one of:
- Reject a deadline longer than the session can live, and say why.
- Clamp it to the real expiry and report the clamped value.
- Keep accepting it, but display
min(closes_at, expires_at) so the reported figure never exceeds what the relay will deliver.
Whichever is chosen, shell list's CLOSES column should reflect the same reconciled value.
Environment
shell 0.7.3 (latest; matches release.json and GitHub latest), installed via curl -fsSL https://shell.online/install | sh, sha256 024510fb1ef100c89bf64e404f27df2ccd323825d4f935ef2f50459121366419. macOS 26.2, arm64.
When you start a share you can give it a deadline with
--auto-close, and the reference invites you to think in long spans — the accepted units include days, weeks, months and years. But an ordinary share only lives twelve hours. The relay stamps every non-persistent session with a twelve-hour expiry at creation and deletes it when that moment arrives. If you ask for anything longer, nothing refuses you and nothing warns you: the command succeeds, the share opens normally, and the CLI reports the deadline you asked for as though it were real.So
shell --auto-close 7d <long job>tells you the session closes in seven days, andshell listkeeps agreeing (CLOSES: in 6d23h) every time you look. Both numbers are fiction. Twelve hours later the relay expires the session and anyone opening the link you handed out gets "Session ended. This sharing link no longer exists."The cause is that two different clocks are reported as one. The CLI computes your requested deadline locally and prints it as
closes_at; the server independently setsexpires_atfrom its own fixed lifetime. Both values travel back in the same payload, side by side, and nothing in the CLI ever compares them.Reproduction
shell 0.7.3, default (non-persistent) session, three consecutive runs:Measured across a range, on a session created at 09:43Z:
--auto-closecloses_atreportedexpires_atactualRoot cause
SESSION_TTL_MS = 12 * 60 * 60 * 1000(worker/index.ts:41), applied at creation asexpiresAt = createdAt + (persistent ? PERSISTENT_TTL_MS : SESSION_TTL_MS)(worker/index.ts:600) and enforced atworker/index.ts:739-741, which callsthis.expire()and returns{exists:false}with 404.On the CLI side the requested deadline is parsed at
cmd/shell/main.go:69and emitted ascloses_atatcmd/shell/main.go:216. It is never compared againstsession.ExpiresAt, which arrives in the same struct — there is no reconciliation anywhere incmd/shell.Scope
--persistentraises the server lifetime to thirty days (PERSISTENT_TTL_MS,worker/index.ts:43), and a seven-day deadline there is genuinely honoured — confirmed,expires_atlands at +29d23h, 2/2 runs. Pushing past thirty days on a persistent session reproduces the same misreporting. The general rule: any deadline exceeding the session's actual lifetime is accepted, echoed back, and silently truncated.Impact
The twelve-hour cap is reasonable; the false confidence built on top of it is the problem. Without
closes_ata user would verify before promising a colleague a week of access. Instead the tool states a specific date, repeats it inshell liston every check, and gives no hint at any point that the figure is unachievable. The mistake surfaces hours later in someone else's browser as a dead link, with nothing in front of the owner connecting that failure to the flag that caused it.Suggested fixes
Any one of:
min(closes_at, expires_at)so the reported figure never exceeds what the relay will deliver.Whichever is chosen,
shell list'sCLOSEScolumn should reflect the same reconciled value.Environment
shell 0.7.3(latest; matchesrelease.jsonand GitHub latest), installed viacurl -fsSL https://shell.online/install | sh, sha256024510fb1ef100c89bf64e404f27df2ccd323825d4f935ef2f50459121366419. macOS 26.2, arm64.