go-cobra-flag-registration
Root cause: cobra.Command.Commands() returns subcommands sorted alphabetically by name, not in the order you called AddCommand. So cmd.Commands()[1] is the second subcommand alphabetically — and cmd.Commands()[1].Flags().String(...) registers the flag on whatever command happens to sit at that index. If subcommand names ever change, get reordered, or a new one is added, flags silently migrate to a different command.
The fix: keep a variable reference to the subcommand at creation time and register flags on that reference. Never index cmd.Commands().
package main
import "github.com/spf13/cobra"
// BAD — breaks the moment subcommand order/names change:
func badRoot() *cobra.Command {
root := &cobra.Command{Use: "app"}
root.AddCommand(&cobra.Command{Use: "zebra", Run: func(c *cobra.Command, a []string) {}})
root.AddCommand(&cobra.Command{Use: "apple", Run: func(c *cobra.Command, a []string) {}})
// Compiles, but lands on "zebra" (alphabetically 2nd), NOT "apple" (2nd defined)
root.Commands()[1].Flags().String("bug-flag", "", "mislocated")
return root
}
// GOOD — variable reference + explicit AddCommand:
func goodRoot() *cobra.Command {
root := &cobra.Command{Use: "app"}
createCmd := &cobra.Command{Use: "create", Run: func(c *cobra.Command, a []string) {}}
root.AddCommand(createCmd) // register first, then wire flags
createCmd.Flags().String("name", "", "the file name")
// safe even though more commands get added later
root.AddCommand(&cobra.Command{Use: "zebra", Run: func(c *cobra.Command, a []string) {}})
return root
}
Key points:
- Declare the *cobra.Command variable, AddCommand it, then call .Flags() on the variable — order of those three lines is irrelevant to correctness.
- The same trap applies to cmd.Commands() for help text, aliases, or any per-subcommand wiring: resolve subcommands by Find([]string{...}) or by variable reference, never by positional index.
Verified empirically with cobra v1.8.1 on Go 1.26.0 (`/tmp/cobra-demo`): **Reproduction (bug path):** ``` Command order: [apple create zebra ] # sorted alphabetically, not insertion order bug: 'app apple --help' contains bug-flag? false bug: 'app zebra --help' contains bug-flag? true # flag landed on the WRONG command fix: 'app create --help' contains name? true # variable-reference fix works ``` **Automated tests — 3 passed** (`go test -v ./...`): | Test | Edge case covered | Result | |---|---|---| | `TestCommandsIndexIsSorted` | Confirms `Commands()[0]` is `alpha` when `zeta` was added first — index ≠ insertion order | PASS | | `TestFixWorksRegardlessOfOrder` | Fix holds when subcommands are added in any order: `[create,zebra]`, `[zebra,create]`, `[apple,create,zebra]` | PASS | | `TestFlagParseOnWrongCommand` | Runtime parse `app apple --bug-flag x` errors with unknown-flag because the flag leaked onto `zebra` — a silent misbehavior, not just a help-text nit | PASS | Additional edge cases reasoned about: renaming a subcommand or inserting a new one before it would silently relocate flags under the index-based approach (no compile error, no runtime error — pure data corruption), whereas the variable-reference approach is immune because the reference never changes.
{"model": "deepseek-v4-flash", "problem_class": "go-cobra-flag-registration", "result": "passed", "tests": 3}