Fix mis-modelled multi-arg options: ecosystem CLIs and macOS tools - #345
Conversation
Audit of option `args` arity across the ecosystem CLIs and macOS-only tools, following the `ln` fix in #341. Every option was checked against the tool's own help output, man page, or source before editing. - lpass share useradd/usermod --read-only/--hidden/--admin: each takes one value that is either true or false, not two values. - hugo new -k/--kind: takes one string; the second declared arg was a copy of `hugo new`'s own path positional. - clojure -m/--main: takes ns-name only; `arg*` is a trailing operand. - stripe config --set: boolean flag; the key and value are positionals of `stripe config`. - pmset -a/-b/-c/-u: scope selectors that take no value; the setting and value operands now live on pmset itself. - vault operator generate-root -pgp-key: one value with two forms (file path or keybase:<username>), modelled like the sibling pgp key options. sips, conda config, elixir/iex and clojure -X/-T were verified as genuinely multi-valued and left unchanged.
|
This PR was generated with Warp. Comment |
Review feedback: the operands removed from `stripe config --set` and `pmset -a/-b/-c/-u` are only valid in combination with those flags, so moving them onto the command and subcommand advertised invocations that are not valid (`stripe config option value`, `pmset displaysleep 10`). The schema cannot express operands conditional on a flag, so they are left unmodelled instead. The flags stay boolean, which was the correct part. pmset's existing root argument is restored untouched. This loses the 19-entry pmset settings list from completion; the PR body says so explicitly rather than dropping it quietly.
There was a problem hiding this comment.
Per-hunk reasoning for this audit, inline on the diff, so each change can be checked against the evidence that settles it without re-deriving it. The PR body carries the same evidence organised by tool, plus the options that were verified and left alone.
Two of those deserve a pointer here, since they have no hunk to anchor to. sips looks exactly like the shapes being corrected in this diff — -s key value, -X tag tagFile, --copyTag srcTag dstTag, --loadTag tag tagFile, -M profile intent, and -c/-p/-z pixelsH pixelsW — but the man page gives each of the eight two values, so all eight are correct as they stand and sips.json is untouched. Same for conda config --append/--prepend,--add/--set/--remove (KEY VALUE, argparse nargs=2), the three elixir/iex options, and clojure -X/-T, whose three declared args match a/fn? [kpath v]* kv-map? in the CLI's own usage.
Responding as wilson: Open session · View in factory
| "description": "Grant write access" | ||
| } | ||
| ] | ||
| "args": { |
There was a problem hiding this comment.
lpass share prints its own usage as useradd [--read-only=[true|false] --hidden=[true|false] --admin=[true|false] SHARE USERNAME, and cmd-share.c declares all three long options with required_argument. Each therefore takes one value drawn from a two-value set, rather than two values. SHARE and USERNAME were already the subcommand's own positionals and are untouched.
| "description": "User can view entry and secrets" | ||
| } | ||
| ] | ||
| "args": { |
There was a problem hiding this comment.
Same shape as --read-only above, settled by the same usage line and the same required_argument declaration.
| "description": "User cannot administer entry" | ||
| } | ||
| ] | ||
| "args": { |
There was a problem hiding this comment.
Same shape as --read-only above.
| "description": "Grant write access" | ||
| } | ||
| ] | ||
| "args": { |
There was a problem hiding this comment.
usermod carries the identical three flags in its usage line and shares the option table in cmd-share.c; see the useradd --read-only comment.
| "description": "User can view entry and secrets" | ||
| } | ||
| ] | ||
| "args": { |
There was a problem hiding this comment.
Same as above.
| "description": "Value for config option" | ||
| } | ||
| ] | ||
| "description": "Set a value for the specified configuration option" |
There was a problem hiding this comment.
--set is boolean: pkg/cmd/config.go declares it BoolVar(&cc.set, "set", false, ...) and reaches WriteConfigField(args[0], args[1]) only under case cc.set && len(args) == 2:, every other case printing help. The key and value are positionals of stripe config accepted only alongside --set, and stripe config option value on its own is not a valid command, so declaring them on the subcommand would advertise one that does not exist.
The schema cannot express operands conditional on a flag, so they are left unmodelled: there is now no completion hint for the key or the value.
| "name": "value" | ||
| } | ||
| ] | ||
| "description": "Settings for all" |
There was a problem hiding this comment.
Apple's pmset.1 synopsis is pmset [-a | -b | -c | -u] [setting value] [...], and its SETTING section says the flags "determine whether the settings apply to battery (-b), charger (wall power) (-c), UPS (-u) or all (-a)" — scope selectors that consume no value of their own. The setting/value pair is pmset's operand, and it had been written into each of the four flags, so this 19-entry settings list existed in four copies.
The pair now goes unmodelled rather than moving to pmset's root argument: a pair is only meaningful after a scope flag, and at the root it would suggest pmset displaysleep 10, which is not valid. That has a real cost — pmset -a <tab> no longer offers displaysleep, hibernatemode and the rest. The list and its descriptions are recoverable from this diff if operands conditional on a flag ever become expressible.
macOS-only, so verified against pmset.1 in apple-oss-distributions/PowerManagement rather than by running the tool.
| "name": "value" | ||
| } | ||
| ] | ||
| "description": "Settings for battery" |
There was a problem hiding this comment.
Second of the four scope selectors, and the second copy of the settings list; see the -a comment.
| "name": "value" | ||
| } | ||
| ] | ||
| "description": "Settings for charger" |
There was a problem hiding this comment.
Same as -a above.
| "name": "value" | ||
| } | ||
| ] | ||
| "description": "Settings for UPS" |
There was a problem hiding this comment.
Same as -a above.
|
I'm starting a first review of this pull request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR corrects several command-signature option arities by collapsing single-value options, removing operands that were incorrectly modeled as option arguments, and preserving value suggestions where the option genuinely accepts one constrained value.
Concerns
- No blocking concerns found. The diff adds no code comments or tests to audit, and
spec_context.mdstates that no approved or repository spec context was found. The changed JSON metadata does not introduce security-sensitive behavior.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz



Audit of option
argsarity across the ecosystem CLIs and macOS-only tools group, following thelnfix in #341. An option'sargsdescribe values the option itself consumes; where our specs put a command's operands, an alternative spelling of one value, or a value set into that array, the model is wrong and completion can misbehave, since the completer resolves the last declared arg regardless of which value is being typed. How visible that is varies per option — several of these corrections are arity-only, with no change a user can see today, and each says so.Every option below was checked against the tool's own ground truth —
--help, the man page, or the tool's source — before any edit.sipsandpmsetare macOS-only and this work was done on Linux, so they were verified against Apple's published man pages rather than by running them; that is called out per option.Where an option turned out to be a boolean flag whose operands belong to the command, those operands are simply removed and left unmodelled. They are not relocated onto the command or subcommand unless they are a genuine positional in their own right, because operands that are valid only in combination with a particular flag cannot be expressed by the schema, and declaring them unconditionally would advertise invocations that are not valid. Both such cases below say plainly what completion data is lost.
Tool versions used: Elixir 1.14.0, Clojure CLI 1.12.5.1664, conda 26.5.3, hugo v0.123.7, stripe 1.50.4, vault 1.18.3, LastPass CLI 1.3.7.
Changed
Category 3 — a value set enumerated as args
lpass share useradd --read-only,--hidden,--adminand the same three underlpass share usermod(6 options,lpass.json)Each declared
["true", "false"]— two arguments. Ground truth, from the CLI's own usage output:and from
cmd-share.cin lastpass/lastpass-cli, where all three are declared withrequired_argument:{"read-only", required_argument, NULL, 'r'}, {"hidden", required_argument, NULL, 'H'}, {"admin", required_argument, NULL, 'a'},One required value each, whose permitted values are
trueandfalse. Each is now a singletrue|falseargument carrying both values as suggestions; the per-value descriptions are preserved.SHAREandUSERNAMEwere already correctly modelled as the subcommands' own positionals, and are untouched.requiresSeparatorwas deliberately not added:required_argumentundergetopt_longaccepts both--read-only=trueand--read-only true.Category 2 — one value modelled as two
vault operator generate-root -pgp-key(vault.json)Declared
[string (filepaths), keybase:user]. Fromvault operator generate-root -help:One value;
keybase:<username>is an alternative form of that value, not a second value. Collapsed to a singlepgp_keyargument withtemplate: filepathsplus akeybase:user1suggestion — the same shape the identicalvault operator init -root-token-pgp-keyoption already uses in this spec.Caveat from the same GUI pass: this option shows nothing in either build, and the untouched
-formatcontrol — four declared suggestions — behaves identically, so multi-character single-dash options may get no value completion at all. Cause unconfirmed and not chased here. The correction stands on the documented arity; it is simply not visible today.Category 1 — operands written into the option
hugo new -k/--kind(hugo.json)Declared
[archetype|default, content-section/file-name.md]. Fromhugo new --help:--kindtakes one string. The second argument was a verbatim copy ofhugo new's own positional, which the subcommand already declares. Removed.This is an arity correction with no observable change in completion today. GUI validation on both builds, with a populated
./archetypes/, produced byte-for-byte identical menus before and after, andhugo new --kind galoffers nothing in either. The archetype generator above is inert: it is written Fig-style as"generators": {"script": ..., "postProcess": ...}, andArgincompletion-metadata/src/fig_types.rshas no field for an inlinegeneratorsobject — onlygeneratorName, which points at a Rust generator. There is nogeneratorNameanywhere inhugo.jsonand no hugo module incommand-signatures/src/generators/, and since the struct does not deny unknown fields, serde drops the key silently. So the removed argument was not hiding anything.That parser gap is worth naming, because it is the actual reason
hugo new -kdoes not complete archetypes and the spec looks like it should: inlinegeneratorsappears 962 times across 142 files incommand-signatures/json/, none of it read. Fixing hugo's archetype completion means porting that script to a Rust generator and referencing it bygeneratorName, not editing this argument.clojure -m/--main(clojure.json)Declared
[ns-name, args (variadic)]. Fromclojure -h:-mtakesns-name;arg*is a trailing operand of the command line, not a value of the option — the "w/args" wording in the description is what the second arg was inferred from. Removed, leavingns-name(with itsisScript) resolvable.stripe config --set(stripe.json)Declared
[option, value].--setis a boolean flag, and the pair are positionals ofstripe configthat are only accepted alongside it. Three independent confirmations:stripe config --helplists--setwith no value type, in contrast to its neighbours--unset stringand--remove-profile string; the example given isstripe config --set color off.pkg/cmd/config.goin stripe/stripe-cli:cmd.Flags().BoolVar(&cc.set, "set", false, "Set a config field to some value"), dispatched bycase cc.set && len(args) == 2:; every other case prints help.stripe config --set colorprints usage and does nothing;stripe config --set color offwritescolor = 'off'.--setis now a boolean flag with no arguments. The key and value are left unmodelled, so there is no longer any completion hint for them. They could not be moved tostripe config's own positionals:stripe config option valuewithout--setis not a valid invocation, so declaring them there would advertise a command that does not exist. The schema cannot express "these operands only after this flag", so the honest outcome is that they go unmodelled.pmset -a,-b,-c,-u(pmset.json)Each declared
[setting, value], with the same 19-entry setting list copied four times. macOS-only; verified against Apple's ownpmset.1from thePowerManagementproject in theapple-oss-distributionsGitHub organisation, since I could not runpmseton Linux. SYNOPSIS:and the SETTING section:
The four flags select a scope and consume no value; the setting/value pairs are pmset's operands and there may be several of them. All four are now boolean flags, and pmset's existing root argument (the
sleepnow/noidle/lock/touchoverrides) is untouched.This drops the 19-entry settings list from the spec, and that is a real loss worth flagging.
displaysleep,disksleep,hibernatemodeand the rest were described there, wrongly attached to the flag and with the namelessvaluearg declared after them; whether they ever reached a menu is not something I measured, and after thehugocase below I will not assert it either way. The data was in the spec and now is not. It could not be moved to pmset's root arguments: a setting/value pair is only meaningful after a scope flag, so declaring it at the root would suggestpmset displaysleep 10, which is invalid, and would change completion for invocations that have nothing to do with these four flags. The schema has no way to say "these operands only after this flag". If conditional operands are worth supporting, this is a concrete case for it — the settings list and their descriptions are recoverable from this PR's diff.Verified correct, left alone
sips— macOS-only; verified against thesips(1)man page as shipped with macOS (mirror: keith.github.io/xcode-man-pages/sips.1.html), cross-checked against ss64.com/mac/sips.html. Both copies agree that all eight genuinely take two values, exactly as the spec declares:-s/--setProperty key value— "Set a property value for key to value."-X/--extractTag tag tagFile— "Write a profile tag element to tagFile."--copyTag srcTag dstTag— "Copy the srcTag element of a profile to dstTag."--loadTag tag tagFile— "Set the tag element of a profile to the contents of tagFile."-M/--matchToWithIntent profile intent-c/--cropToHeightWidth pixelsH pixelsW-p/--padToHeightWidth pixelsH pixelsW-z/--resampleHeightWidth pixelsH pixelsWconda config— fromconda config --help(conda 26.5.3), each isnargs=2:elixirandiex— fromelixir --help(Elixir 1.14.0):Two values each.
iex --helpstates "It accepts all other options listed byelixir --help", so the three identical options oniexare settled by the same lines.clojure -Xand-T— fromclojure -h(CLI 1.12.5.1664):The three declared args (
a/fn, variadickvs,kv-map) match the documented shape in order, including thekv-map?trailing map added in the 1.12 CLI. Genuinely multi-valued; unchanged.Unresolved
None. Every option in this group resolved against ground truth.
Noticed but out of scope (not touched)
sips -i/--addIcondeclares aniconfilepaths argument, but the man page shows it taking none: "-i/--addIconAdd a Finder icon to image file." A single spurious arg rather than the ≥2 class, and outside the assigned list.conda.jsonuses"name": "--prepend, --add"as one literal option name (and"-c, --channel"elsewhere) instead of a name array, so the aliases are unlikely to match.clojure -Tdeclaresa/fnasisOptional, butclj -hshows it required for-T(optional only for-X).clojuremodels no command-level positional for thepathmain-opt or the trailingarg*."generators"objects are not read by this crate at all — 962 occurrences across 142 files incommand-signatures/json/, silently dropped by serde becauseArgonly hasgeneratorName.hugo new -kis one user-visible instance; see that section above.Testing
cargo test -p warp-command-signatures— 176 passed, 0 failed, 0 ignored. All six edited specs are clean undernpm run format.