fix: correct exit codes, unknown subcommands, and table overflow - #39
Merged
Conversation
An audit of the CLI surface turned up nine defects, all of them in how the binary presents itself rather than in what it asks the API for. Three of them break scripted use: A mistyped subcommand succeeded. Cobra's legacyArgs only rejects unknown commands on the root command — for a parent that itself has a parent it returns nil, so cobra fell through to "not runnable", printed help, and returned no error. `namecom domain regsiter example.com` exited 0, which means `namecom domain regsiter foo.com && deploy` deployed. Every group is now wrapped in cmdutil.GroupCmd, which rejects an unknown subcommand as a usage error and reuses cobra's suggestion list. A group invoked bare still prints help and exits 0, which is what browsing looks like. Invocation mistakes exited 1 instead of the documented 2. Two causes: the validators in cmd/cmdutil/validate.go returned bare fmt.Errorf values, and cobra checks required flags inside execute(), after SetFlagErrorFunc has had its chance and with no hook of its own. So `--type ZZZ` and a missing `--answer` reported the same code as a 500 while `--badflag` beside them reported 2. Validators now build UsageError directly; cobra's own messages are classified in one documented place, asserted by a test that fails if an upgrade rewords them. --dry-run promised to "print the API request that would be sent without executing it", but only the 28 write paths consult it — reads always called the API. Rather than change what 38 commands do, the flag now says what it does. The rest are presentation. Tables were rendered at natural width with no regard for the terminal: 113 columns for `domain list` against an 80-column pane, where the rounded borders wrap into fragments. Trailing columns are dropped until the table fits, a footer names what went, and --wide opts out; a pipe has no width to fit and keeps everything. Relative dates widen their unit past a quarter, because "in 2750 days" told a reader nothing. Examples moved above the flag tables. Command groups no longer advertise themselves as `namecom domain [flags]`. `dns create --type` lists CAA, which the validator has always accepted. Non-string flag defaults print unquoted. CLAUDE.md's command tree had drifted — no config, contact, status, open, or version, and `vanity/` where the command is `vanity-ns`.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Codecov put patch coverage at 89.6%. Most of the miss was pre-existing colour branches and Execute's os.Exit path, but three of the fixes in this branch genuinely had no test: the Examples/Flags ordering, the command-group usage line, and the non-string default formatting. All three are the kind of thing that reverts silently when someone edits the template for an unrelated reason. The expiry-threshold test is the reason for the small refactor. Written the obvious way it asserted on rendered ANSI prefixes — and lipgloss degrades every style to a no-op off a TTY, so each expected prefix was the empty string and all three cases passed without checking anything. expiryStyle is split out of ExpiryDate so the thresholds can be compared as values instead. Every new assertion was mutation-checked: reverting the week threshold, the unit-widening threshold, the column dropping, the group usage line, the default quoting, the required-flag prefix, the Examples ordering, and the unknown-subcommand rejection each fail at least one test.
This was referenced Aug 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An audit of the CLI surface — how the binary presents itself, rather than what
it asks the API for — turned up nine defects. All nine are fixed here.
Breaks scripted use
A mistyped subcommand succeeded. Cobra's
legacyArgsonly rejects unknowncommands on the root command; for a parent that itself has a parent it
returns
nil, so cobra fell through to "not runnable", printed help, andreturned no error.
namecom domain regsiter foo.com && deploydeployed. Every group now goesthrough
cmdutil.GroupCmd, which rejects an unknown subcommand as a usageerror and reuses cobra's suggestion list. Invoking a group bare still prints
help and exits 0.
Invocation mistakes exited 1 instead of the documented 2. Two causes: the
validators in
cmd/cmdutil/validate.goreturned barefmt.Errorfvalues, andcobra checks required flags inside
execute()— afterSetFlagErrorFunchashad its chance, with no hook of its own.
dns create example.com --type ZZZ --answer 1.2.3.4dns create example.com(required flags missing)dns list example.com --badflagdomain get(arg missing)A script watching for exit 1 could not tell a typo from a server error.
Validators now build
UsageErrordirectly; cobra's own messages areclassified in one documented place, asserted by a test that fails loudly if an
upgrade rewords them.
--dry-runwas silently ignored on reads. It promised to "print the APIrequest that would be sent without executing it", but only the 28 write paths
consult
IsDryRun—namecom domain list --dry-runmade a live authenticatedcall and returned real account data. Rather than change what 38 read commands
do, the flag now describes what it actually does.
Terminal output
Tables overflowed the terminal. They rendered at natural width regardless
of it —
domain list113 columns,order list99,dns list87 — so in an80-column pane the rounded borders wrapped into fragments. Trailing columns
are now dropped until the table fits, since callers order columns
most- to least-important, and a footer names what went rather than letting it
vanish:
The new
--wideopts out. A pipe has no width to fit and keeps every column,so scripts reading table output are unaffected.
Relative dates only spoke days. A domain paid through 2034 read
in 2750 days. Days stay exact inside a quarter, where a renewal decision is actuallypending; past that the unit widens to months, then years.
Help and docs
Examples:moved from below the flag tables and the "see all globaloptions" footer to directly under the usage line.
namecom domain [flags], an invocation that doesnothing; they now show
namecom domain <command>.dns create --typeomittedCAA, which the validator has always accepted.default 300, notdefault "300".CLAUDE.md's command tree had drifted: noconfig,contact,status,open, orversion, andvanity/where the command isvanity-ns.Tests
TestEveryGroupRejectsUnknownSubcommandswalks the real command tree, so agroup added later without
GroupCmdfails rather than silently regressing.TestClassifyCobraUsagepins the cobra message strings the exit-codemapping depends on.
TestTableFitsTerminalWidthcovers dropping,--wide, and the unconstrainedpipe case.
TestRelativeTimeWidensUnitpins the unit thresholds.make test,make lint, andmake buildall pass.