Skip to content

Validate flags/positionals before the commands/nodes that own them - #629

Open
hexonal wants to merge 1 commit into
alecthomas:masterfrom
hexonal:fix-validate-flags-before-command
Open

Validate flags/positionals before the commands/nodes that own them#629
hexonal wants to merge 1 commit into
alecthomas:masterfrom
hexonal:fix-validate-flags-before-command

Conversation

@hexonal

@hexonal hexonal commented Jul 22, 2026

Copy link
Copy Markdown

Fixes #611.

Bug

Context.Validate() iterates c.Path in trace order and calls each element's Validate() as it's encountered. Since a command's own Path entry is appended before its flags' entries (flags come later in the token stream, e.g. command --flag value), the command's Validate() ran before its flags' Validate() — so a command's validator couldn't rely on its own flags already being validated:

kong.Must(&CLI{}).Parse([]string{"command", "--flag", "value"})
// validate command   <- ran first
// validate flag       <- should have run first

Fix

A node's own flags/positionals always appear on the path contiguously right after that node, before the next node entry. So each node's own validation is now deferred (via a single pendingNode variable) until that boundary — or the end of the path — is reached, running it right after its own flags/positionals instead of before them.

This only reorders each node relative to its own directly-owned flags. The relative order between different nodes on the path is unaffected — a parent command still validates before its child command, exactly as before. (An earlier version of this fix used a flat two-pass split over the whole path instead, which had a wider side effect: an ancestor command's validation could get delayed by an unrelated descendant command's flags. This version avoids that by only ever holding back the most recently encountered node, not the whole path.)

Known limitation

Resolve() appends resolver-derived flag values (e.g. from a config-file Resolver) to the end of c.Path without recording which node each one belongs to. Because of that, a node whose flags come from a Resolver (rather than the command line) can still end up deferred past other nodes' resolver-supplied flags too — noted in a code comment. Fixing this properly would mean also threading ownership through Resolve()'s appended entries, which felt like more than this fix should take on; happy to follow up separately if that's worth doing.

Tests

  • TestValidateCmdRunsAfterItsFlags — the issue's exact repro (single command, one flag).
  • TestValidateNestedCmdRunsAfterOwnFlagsOnly — two-level nesting (app flag, parent command + its own flag, child command + its own flag), confirming each level's own flag validates immediately before it while app → parent → child ordering is preserved.

Both confirmed to fail against pre-fix code and pass with the fix.

Context.Validate() iterated c.Path in trace order and called each
element's Validate() as encountered. Since a command's own Path entry is
appended before its flags' entries (flags are parsed after the command
name in the token stream), a command's Validate() ran before its flags'
Validate() - so a command validator couldn't rely on its own flags
already being validated.

A node's own flags/positionals always appear on the path right after
that node, before the next node entry. So each node/command's own
validation is now deferred until that boundary (or the end of the path)
is reached, running it right after its own flags/positionals rather than
before them. This only reorders each node relative to its own directly-
owned flags; the relative order between different nodes on the path
(e.g. a parent command still validates before its child command) is
unaffected. Fixes alecthomas#611.
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.

Command is validated before flags

1 participant