Development record
2026-08-17 — Command parents owe TWO guarantees, not one (iss-266, #293).
2026-08-17 — Command parents owe TWO guarantees, not one (iss-266, #293).
A parent refuses a mistyped sub-verb only if it is Runnable AND declares an
Args validator; missing either exits 0 silently (no RunE ⇒ cobra returns
flag.ErrHelp before ValidateArgs; Args nil ⇒ legacyArgs falls through to
ArbitraryArgs). The house convention is therefore RunE: helpRunE plus an
explicit validator on every parent, asserted structurally over the live
command tree rather than a hand-kept list. capture and intent are a
named exemption — their positional is free text, guarded by their own
suspected-typo check — and the exemption list is itself tested against
reality so a stale name cannot quietly drop a parent from the sweep.
Consequence accepted and filed as iss-267: exit 2 is the blocking status
on the hook plane, so hook sub-verb names are now a compatibility contract
and a future rename must carry an alias rather than rely on the old
silent-exit-0 cushion. Rejected: fixing only the parents named in the issue
(the same hole would return with the next parent added), and asserting the
behaviour with a hand-maintained list of parents (it had already drifted —
identity and intent audit were absent yet not exempt).
Frontmatter
| id | dcn-2608170000000003 |
|---|---|
| type | decision |
| date | 2026-08-17 |
.abcd/work/decisions/dcn-2608170000000003-command-parents-owe-two-guarantees-not.md
open on GitHub ↗ · commit history ↗
mentions2
- iss-266cobra parents with no RunE are not Runnable, so cobra prints help and exits 0 WITHOUT validating args: an unknown sub-verb reads as success to a script. Confirmed on 'abcd docs nonsense' and 'abcd guard nonsense' (both exit 0); 'abcd memory nonsense' and 'abcd history nonsense' exit 1 because those parents have a RunE. spc-30 fixed the disembark parent (a retired sub-verb spelling must refuse, not silently succeed) by giving it a RunE that prints help, so cobra.NoArgs then refuses a stray token at exit 2. The same one-line fix is owed to every other RunE-less parent — sweep them so no unknown sub-verb anywhere exits 0.
- iss-267Hook sub-verb names are now a compatibility contract, not a cushion. iss-266 makes every parent runnable, so an unknown sub-verb exits 2 instead of printing help at exit 0. On the Claude Code hook plane exit 2 is the BLOCKING status: exit 2 on UserPromptSubmit blocks the prompt, and exit 2 from 'guard hook' on PreToolUse blocks every Bash tool call -- and the PreToolUse wrapper in hooks/hooks.json treats 2 as a recognised code, so its 'FAILED TO RUN ... UNGUARDED' safety net does not fire. hooks.json ships with the plugin git clone while the binary is fetched from the latest release by hooks/bootstrap.sh, so the two can skew. No live instance today (every sub-verb hooks.json names exists at v0.5.1), and the fail-closed direction is intended. But a future rename of 'guard hook' or any 'hook <sub-verb>' must now carry an alias rather than rely on the old silent exit-0 cushion: against a skewed hooks.json/binary pair it would brick a session where it previously degraded silently. Found by the security review of the iss-266 fix.