Skip to content

fix(cli): a program option is read only before the command, so a value that begins with -v is never the version - #981

Merged
kid7st merged 1 commit into
mainfrom
fix/cli-version-only-before-command
Nov 8, 2026
Merged

kid7st merged 1 commit into
mainfrom
fix/cli-version-only-before-command

Conversation

@kid7st

@kid7st kid7st commented Nov 8, 2026

Copy link
Copy Markdown
Collaborator

Root cause of cli-invoke.test.ts's intermittent failure ("continues a served session with the model this run names", once on Node 26), and the fix.

Root cause

commander reads a program option anywhere in the arguments by default, a subcommand's option values and operands included (its documented default; enablePositionalOptions() turns it off). The program's only option is -v, --version. So any argument after the command that begins with -v printed the version and exited 0, doing nothing:

Command Before
fastagent invoke --session -vQ2jhwjklm.3 hi 0.30.0, exit 0, nothing sent
fastagent invoke "-very urgent: summarize" 0.30.0, exit 0
fastagent content add --name -vendor ./x 0.30.0, exit 0
fastagent invoke --model -v hi 0.30.0, exit 0

A session id is the storage's incarnation (randomBytes(8).toString("base64url")) and pi-durable's id, so every session of one storage begins with the same characters, and 1 storage in 4096 begins with -v. Then every invoke --session <id> against it printed the version: what the test saw as a second run with an empty stderr. Of the 64 base64url characters after a -, only v does this.

The fix

program.enablePositionalOptions() in the kernel: a program option is read before the command and nowhere else, which is what docs/cli.md already says ("-v/--version comes before any command"). Now invoke --session -v… and --model -v give the command their values, and an operand that begins with - after the command is refused as an unknown option (exit 2, with the message), not answered with the version. fastagent -v, --version and each command's --help are unchanged.

Tests

cli-kernel.test.ts, through buildProgram: a value after the command that begins with -v reaches the command; an operand that does is refused as an unknown option, without the version; -v before the command is still the version. The first two failed before the fix (the version was printed, exit 0). Mutation: without enablePositionalOptions() both fail.

Not reproduced by running the original test: it needs one storage in 4096, and 150 targeted runs and 10 shard runs in a Linux container (Node 26.13.0) did not draw one. The mechanism was found by trying the arguments it passes.

@kid7st
kid7st requested a review from Sukitly as a code owner November 8, 2026 17:22
@kid7st kid7st self-assigned this Nov 8, 2026
@github-actions github-actions Bot added the bug Something isn't working label Nov 8, 2026
@kid7st
kid7st merged commit 434a19c into main Nov 8, 2026
12 checks passed
@kid7st
kid7st deleted the fix/cli-version-only-before-command branch November 8, 2026 17:28
@kid7st kid7st mentioned this pull request Nov 8, 2026
kid7st added a commit that referenced this pull request Nov 8, 2026
## Release v0.30.1

Bump `@fastagent-sh/fastagent` from 0.30.0 to 0.30.1. Two fixes to
0.30.0; nothing else.

## Fixed

- **An argument after the command that begins with `-v` printed the
version and did nothing** (#981). commander read the program's `-v,
--version` anywhere in the arguments by default, so `fastagent invoke
"-very urgent: …"`, `--name -vendor` and `invoke --session <id>` with an
id beginning with `-v` (1 storage in 4096) printed `0.30.0` and exited 0
having done nothing. The program's options are now read only before the
command, as `docs/cli.md` says; an operand that begins with `-` after
the command is refused as an unknown option (exit 2).
- **A stopped or replaced step could report the wrong end** (#980).
Stopping a ticket's step (`--check <ticket> --stop`), replacing it with
a newer attempt, or giving up on its start signalled the step and wrote
the reason after; a step that heard the consequences first (its host CLI
killed by the same signal, as `signal-exit` lets it on Linux) recorded
its own failure, and that one stayed. A ticket's end is now written
once, before what it decides is done, and nothing the step writes after
reopens it.

## Verification

- `npm run lint`, `npm run typecheck`, `npm test` (195 files, 2382
passed, 2 skipped), `npm pack --dry-run --json`.
- The ticket and CLI tests also passed three times under load in a Linux
container (Node 26.13.0), where the step-ordering failure was measured.

## After merge

Create and publish the GitHub Release `v0.30.1` from the merged commit,
with the sections above as its notes. The protected publish workflow
verifies and publishes the package to npm via Trusted Publishing. This
PR itself does not tag or publish.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant