Skip to content

fix(cli): accept the documented argument order on every command - #112

Merged
frahlg merged 1 commit into
mainfrom
112-share-flag-order
Aug 30, 2026
Merged

fix(cli): accept the documented argument order on every command#112
frahlg merged 1 commit into
mainfrom
112-share-flag-order

Conversation

@frahlg

@frahlg frahlg commented Aug 30, 2026

Copy link
Copy Markdown
Member

mir share <machine> --ttl 1h — the exact order the command's own usage string documents — did not work. Go's flag.FlagSet.Parse stops at the first positional, so --ttl 1h landed in fs.Args() and tripped the arity check; only mir share --ttl 1h <machine> parsed. A user following our own help text got a refusal.

This is the third outing for the same trap: #45 hit it on mir attach <name> --dir X, and G1d solved it for machine revoke alone with a bespoke argument peel. So the fix is one mechanism, applied everywhere, plus a test that walks every command.

The mechanism

parseArgs (go/internal/cli/shared.go) parses a FlagSet against args and returns the positionals, accepting flags and positionals in any order — it re-parses after each positional until both sides of every positional are consumed. A literal -- still ends flag parsing. Every command that takes positionals AND flags now goes through it, so the bug class cannot come back one command at a time.

Audit — every subcommand that mixes positionals and flags

Command Documented order Worked before? Fixed
mir attach <machine…> [flags] attach box --dir X ❌ resolved a machine literally named --dir
mir run [flags] <machine> <command…> run --dir X box ls -la — (deliberate exception, see below)
mir share <machine> [--ttl …] share box --ttl 1h usage error (#112)
mir share revoke <id> [flags] share revoke abc123 --dir X ❌ usage error
mir share ls [flags] share ls --dir X ✅ (no positionals)
mir join <code> [flags] join CODE --dir X ❌ usage error
mir pair [<code>] [flags] pair CODE --dir X ❌ usage error
mir machine rename <name> <new> [flags] machine rename box newbox --dir X ❌ usage error
mir machine revoke <name> [--yes] machine revoke box --yes ⚠️ worked via a bespoke peel (G1d); machine revoke --yes box --dir X still failed ✅ moved onto the shared helper
mir up, list/ls, doctor, keygen, add-machine, enroll, pair-dev, update, identity …, wallet … flags only
mir-signal flags only

mir run stays flags-first on purpose: everything after <machine> is the remote command, so mir run box ls -la must hand -la to ls, not to mir. Its usage line now says exactly that, and a test pins it.

Usage strings

Corrected where they were wrong or hardcoded: run now documents flags-first and that the tail is the remote command; attach, join, machine …, and share print the running binary (a.binary) instead of a literal mir, so the mir-agent shim reads right.

Detach copy

The two attach entry paths described the gesture differently, and each dropped what the other said. They now share an opening — client.AttachHint: attached to <name> — Ctrl-C goes to the shell — and each adds only the way out it actually has:

  • bare mir attach box: [mir] attached to box — Ctrl-C goes to the shell; close the client to detach (the session keeps running)
  • from the mir overview: [mir] attached to box — Ctrl-C goes to the shell; Ctrl-O then d comes back to your machines

The overview's gesture exists only while the overview is running, so a bare attach does not claim it. The guide's Ctrl-O d is now spelled Ctrl-O then d, matching the banner.

Consent gates unchanged

share --write still makes you type the machine name; share still refuses without a TTY; machine revoke still asks, or wants --yes. Two tests pin the share refusals in both argument orders.

Tests

go/internal/cli/argorder_test.go — a table over the real dispatch runs the documented order AND Go's native flags-first order for every command touched, asserting each reaches the same honest refusal and never a usage line. Verified the new rows fail on the pre-fix code (7 documented-order rows plus the share/TTY test) and pass after. Plus a unit table on parseArgs (flags before/after/between positionals, -- terminator) and a copy guard on AttachHint.

cd go && go test ./... green, gofmt -l . empty, go vet ./... clean, cd web && npm test 167/167. No testdata/ vector changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KeiotDVE94wEzvc7wcvm1y


Note

Low Risk
CLI parsing and user-facing help only; consent gates and run's remote-command semantics are explicitly preserved and regression-tested.

Overview
Fixes invocations like mir share box --ttl 1h that matched help text but failed because Go's flag package stops parsing at the first positional. A shared parseArgs helper re-parses after each positional so flags can appear before, after, or between arguments (with -- still forcing the rest to stay positional).

parseArgs is wired into attach, share, share revoke, join, pair, and machine rename/revoke (replacing ad-hoc peeling on revoke). mir run stays flags-first on purpose so remote command flags are not swallowed; its usage line documents that. Several usage strings now use a.binary instead of a hardcoded mir.

Attach stderr copy is split: client.AttachHint covers machine name and Ctrl-C; bare attach says to close the client to detach, overview attach adds Ctrl-O then d, and the guide uses the same wording.

argorder_test.go tables real dispatch for documented vs flags-first argv (must reach real errors, never usage:), unit-tests parseArgs, and checks share TTY consent and AttachHint copy.

Reviewed by Cursor Bugbot for commit 10c566d. Bugbot is set up for automated code reviews on this repo. Configure here.

`mir share <machine> --ttl 1h` — the order the command's own usage string
shows — was refused. Go's flag.FlagSet stops at the first positional, so
`--ttl 1h` landed in fs.Args() and tripped the arity check; only the
flags-first spelling parsed. The same trap hit `mir attach <name> --dir X`
(#45), and G1d had fixed it for `machine revoke` alone with a bespoke
argument peel.

One mechanism now, not one per command: parseArgs (cli/shared.go) parses
flags and positionals in any order and every command that takes both goes
through it — attach, share, share revoke, join, pair, machine rename,
machine revoke. `mir run` keeps flags-first on purpose: everything after
<machine> is the remote command, so `mir run box ls -la` must hand `-la`
to ls; its usage line now says so. Usage strings print the running binary
instead of a hardcoded "mir", so the mir-agent shim reads right.

Consent gates are untouched: `share --write` still makes you type the
machine name, `share` still refuses without a TTY, `machine revoke` still
asks or wants --yes.

Copy: the two attach banners described the detach gesture differently and
each omitted what the other said. They now share an opening —
client.AttachHint, "attached to X — Ctrl-C goes to the shell" — and each
adds only the way out it really has: closing the client for a bare
`mir attach`, Ctrl-O then d from the overview, which is the one place that
gesture exists.

Tests: a table over the real dispatch runs both orders for every command
touched, so this class cannot regress a third time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeiotDVE94wEzvc7wcvm1y
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T14:38:41.634146Z 10c566d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10c566d731

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread go/internal/cli/shared.go
Comment on lines +103 to +105
if a == "--" {
args, literal = args[:i], args[i+1:]
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve -- when it is a flag's argument

When a non-boolean option legitimately has the literal value --—for example, mir share --session -- box or mir attach --dir -- box—Go's documented -flag x syntax consumes that token as the option value. This unconditional pre-scan instead removes it as a terminator before FlagSet.Parse can interpret the surrounding flag, leaving the option without an argument and exiting with a parsing error; detect the terminator while respecting tokens consumed as flag values so these previously valid flags-first invocations continue to work.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Cursor Bugbot completed successfully with no findings that need human review, and no approval policy requires extra review. No reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@frahlg
frahlg merged commit c1d0474 into main Aug 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant