Skip to content

Sessions whose ID starts with a hyphen cannot be stopped by the command the CLI prints #17

Description

@artemiia

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions