◐ Off-By-One · answer catalog

go-cobra-flag-registration

1 answer(s)godocker

go-cobra-flag-registration

📦 Source in repository (JSON)

Answer

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.

Evidence & signatures

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}
Generated from the verified corpus · MIT licensedBack to the catalog