About one session in every sixty-four is given an ID that begins with a hyphen, and when that happens the session cannot be stopped using the command the CLI itself prints. On starting a share, shell displays a card ending in Stop shell kill <id>. If the ID happens to begin with -, copying that line verbatim fails: Go's flag package sees the leading hyphen and treats the ID as an undefined flag, so the argument is rejected before any session lookup runs. The user gets flag provided but not defined, exit status 2, and the session keeps running. Nothing in the error or in shell help kill mentions that a -- separator would work, so the only discoverable way out is shell kill --all, which stops every other session too.
What makes this particularly confusing is that the same card prints two commands for the same ID and only one of them works. shell attach <id> handles a hyphen-leading ID fine, because attach never builds a flag set. shell kill <id> does build one, in order to support --all, and that is what rejects the ID.
Reproduction
Live session on 0.7.3:
Link https://shell.online/s/-Vpl4RkgWAFDWbavX8FafE97oqHtw52p#salt=...
Session -Vpl4RkgWA · background
Rejoin shell attach -Vpl4RkgWA
Stop shell kill -Vpl4RkgWA
Running the printed Stop command:
$ shell kill -Vpl4RkgWA
flag provided but not defined: -Vpl4RkgWA
Usage: shell kill <session-id-or-prefix>
shell kill --all
$ echo $?
2
The session is still running afterwards. The undocumented escape works:
$ shell kill -- -Vpl4RkgWA
Stopping -Vpl4RkgWA sleep 500
Confirmed on two independent live sessions. The parser behaviour is also reproducible without waiting for one, since a rejected argument never reaches lookup:
shell kill -abc123xyz -> exit 2, "flag provided but not defined: -abc123xyz" (rejected by parser)
shell kill zzNOTREAL9 -> exit 1, "shell: no active sessions" (lookup ran)
shell attach -abc123xyz -> exit 1, "no active session matches \"-abc123xyz\"" (lookup ran)
Root cause
runSessionKill (cmd/shell/sessions.go:318) builds a flag set so it can offer --all:
flags := flag.NewFlagSet("shell kill", flag.ContinueOnError)
all := flags.Bool("all", false, "stop every active local session")
...
if err := flags.Parse(arguments); err != nil { ... return 2 }
Go's flag package stops treating arguments as flags only at the first non-flag token, and a session ID beginning with - looks like a flag. flags.Parse fails and the function returns 2 at sessions.go:328, well before loadActiveLocalSessions() is called.
runSessionAttach (cmd/shell/sessions.go:90) has no flag set at all — it simply checks len(arguments) != 1 — so the same ID passes straight through. That is the entire asymmetry.
The -- separator inverts it: flag.Parse honours -- as end-of-options, so kill -- <id> works, while attach counts arguments literally and ["--", "<id>"] fails its length check.
Frequency
Not a rare edge case. worker/index.ts:597 generates IDs with randomToken(24): 24 random bytes, btoa, then +→- and /→_. The first character is byte0 >> 2, uniform across the 64-symbol base64url alphabet, and - is index 62.
Simulating that exact algorithm 400,000 times gives 1.579% leading - (theory 1/64 = 1.5625%), uniform across all 64 leading characters. Cumulative exposure:
| Sessions started |
Chance of hitting it at least once |
| 10 |
14.6% |
| 50 |
54.5% |
| 100 |
79.3% |
Three real instances appeared during a single afternoon of testing: -xeFHWfc14…, -311JpgtnL…, -Vpl4RkgWA….
Impact
The user is not doing anything unusual — they are running the command the tool just told them to run. The failure text names no remedy, shell help kill never mentions a -- separator, and the only documented alternative (shell kill --all) destroys every other active share to recover one.
Suggested fixes
- Treat the first positional argument as the session ID before flag parsing, or pre-scan for
--all and pass the remainder through untouched.
- Or normalise internally by inserting
-- ahead of a lone non-flag argument, the same shape as the existing normalizeAutoCloseArguments helper.
- Or exclude
- (and ideally _) from the first character when generating IDs in randomToken, which removes the class of problem for every current and future subcommand.
- Regardless of the above: make
attach and kill agree on --, since today one requires it and the other rejects it.
Environment
shell 0.7.3 (latest; matches release.json and the GitHub latest release), installed via curl -fsSL https://shell.online/install | sh, sha256 024510fb1ef100c89bf64e404f27df2ccd323825d4f935ef2f50459121366419. macOS 26.2, arm64.
About one session in every sixty-four is given an ID that begins with a hyphen, and when that happens the session cannot be stopped using the command the CLI itself prints. On starting a share,
shelldisplays a card ending inStop shell kill <id>. If the ID happens to begin with-, copying that line verbatim fails: Go's flag package sees the leading hyphen and treats the ID as an undefined flag, so the argument is rejected before any session lookup runs. The user getsflag provided but not defined, exit status 2, and the session keeps running. Nothing in the error or inshell help killmentions that a--separator would work, so the only discoverable way out isshell kill --all, which stops every other session too.What makes this particularly confusing is that the same card prints two commands for the same ID and only one of them works.
shell attach <id>handles a hyphen-leading ID fine, because attach never builds a flag set.shell kill <id>does build one, in order to support--all, and that is what rejects the ID.Reproduction
Live session on
0.7.3:Running the printed Stop command:
The session is still running afterwards. The undocumented escape works:
Confirmed on two independent live sessions. The parser behaviour is also reproducible without waiting for one, since a rejected argument never reaches lookup:
Root cause
runSessionKill(cmd/shell/sessions.go:318) builds a flag set so it can offer--all:Go's
flagpackage stops treating arguments as flags only at the first non-flag token, and a session ID beginning with-looks like a flag.flags.Parsefails and the function returns 2 atsessions.go:328, well beforeloadActiveLocalSessions()is called.runSessionAttach(cmd/shell/sessions.go:90) has no flag set at all — it simply checkslen(arguments) != 1— so the same ID passes straight through. That is the entire asymmetry.The
--separator inverts it:flag.Parsehonours--as end-of-options, sokill -- <id>works, while attach counts arguments literally and["--", "<id>"]fails its length check.Frequency
Not a rare edge case.
worker/index.ts:597generates IDs withrandomToken(24): 24 random bytes,btoa, then+→-and/→_. The first character isbyte0 >> 2, uniform across the 64-symbol base64url alphabet, and-is index 62.Simulating that exact algorithm 400,000 times gives 1.579% leading
-(theory 1/64 = 1.5625%), uniform across all 64 leading characters. Cumulative exposure:Three real instances appeared during a single afternoon of testing:
-xeFHWfc14…,-311JpgtnL…,-Vpl4RkgWA….Impact
The user is not doing anything unusual — they are running the command the tool just told them to run. The failure text names no remedy,
shell help killnever mentions a--separator, and the only documented alternative (shell kill --all) destroys every other active share to recover one.Suggested fixes
--alland pass the remainder through untouched.--ahead of a lone non-flag argument, the same shape as the existingnormalizeAutoCloseArgumentshelper.-(and ideally_) from the first character when generating IDs inrandomToken, which removes the class of problem for every current and future subcommand.attachandkillagree on--, since today one requires it and the other rejects it.Environment
shell 0.7.3(latest; matchesrelease.jsonand the GitHub latest release), installed viacurl -fsSL https://shell.online/install | sh, sha256024510fb1ef100c89bf64e404f27df2ccd323825d4f935ef2f50459121366419. macOS 26.2, arm64.