go-cobra-dead-persistent-flag
Root cause. Cobra binds persistent flags to variables only during ParseFlags, which runs inside ExecuteContext — after main.go's run() builds the registry. Reading the flag-bound variable before execution always yields "", so --data-dir was a dead flag and the registry silently fell back to config/default. Independently, the dashboard server derived paths from the hardcoded app.DefaultDataDir() instead of the registry, so even a config data_dir pointing elsewhere was ignored by everything the server touched.
Fix 1 — pre-parse os.Args before building the registry (flagparse.go):
// parseDataDirFlag pre-parses os.Args for the root persistent flag
// --data-dir. Cobra only parses flags during ExecuteContext, which runs
// AFTER registry construction, so reading the bound variable early is
// always empty (dead flag). This recovers the value before that point.
func parseDataDirFlag(args []string) string {
dataDir := ""
for i := 0; i < len(args); i++ {
arg := args[i]
switch {
case arg == "--":
return dataDir // everything after "--" is positional
case arg == "--data-dir":
if i+1 < len(args) {
dataDir = args[i+1]
i++ // consume the value
}
case strings.HasPrefix(arg, "--data-dir="):
dataDir = strings.TrimPrefix(arg, "--data-dir=")
}
}
return dataDir
}
Wire it into run() (precedence: flag > config > default):
func run(cfgPath string, args []string) (*Registry, error) {
cfg := loadConfig(cfgPath)
dataDir := parseDataDirFlag(args) // pre-parse, NOT dataDirFlag
if dataDir == "" {
dataDir = cfg.DataDir
}
return NewRegistry(dataDir), nil
}
func main() {
registry, err := run(defaultConfigPath, os.Args[1:]) // registry built first
if err != nil { /* exit */ }
if err := newRootCmd(registry).ExecuteContext(context.Background()); err != nil { /* exit */ }
}
Keep the persistent flag declared — cobra still parses it during ExecuteContext and --help shows it; pre-execution consumers just don't read the bound variable:
root.PersistentFlags().StringVar(&dataDirFlag, "data-dir", "", "override configured data dir")
Fix 2 — dashboard derives paths from the registry (dashboard.go):
func (d *Dashboard) DataDir() string { return d.registry.DataDir() } // was: DefaultDataDir
No repo existed in the workspace, so I built a minimal faithful reproduction (`/tmp/cobra-dead-flag`, real `github.com/spf13/cobra v1.10.2`, Go 1.26) with the exact reported wiring: `run()` constructs the registry before `ExecuteContext`; dashboard holds a registry. **RED phase (buggy code) — both bugs reproduced:** ``` --- FAIL: TestE2E_FlagOverridesConfigDataDir registry.DataDir() = "/cfg/data", want "/flag/data" --- FAIL: TestE2E_DashboardUsesRegistryDataDir dashboard.DataDir() = "/var/lib/myapp", want registry.DataDir() = "/cfg/data" ``` **GREEN phase (fixed code) — `go vet` clean, all pass:** | Test | Cases | Verifies | |---|---|---| | `TestParseDataDirFlag` | 11 subtests | `--data-dir=/x` and `--data-dir /x` forms; flag before/after subcommand; interleaved with other flags; absent/nil; empty value `--data-dir=` falls back; missing value at end; `--` separator (positional, not flag); `--data-dirx=/x` prefix not matched; repeated flag last-wins (matches pflag) | | `TestE2E_FlagOverridesConfigDataDir` | 1 | flag wins over config `data_dir` | | `TestE2E_DashboardUsesRegistryDataDir` | 1 | server uses `registry.DataDir()`, never `DefaultDataDir` | | `TestE2E_Precedence` | 1 | flag > config > default | | `TestE2E_CobraParsingAgreesWithPreParse` | 1 | real cobra `ExecuteContext` parses `--data-dir` to the same value as `parseDataDirFlag` — pre-parse doesn't conflict with cobra | **CLI end-to-end** (config `data_dir = /tmp/e2e-run/cfgdata`, i.e. pointing elsewhere): ``` $ myapp dashboard → dashboard data dir: /tmp/e2e-run/cfgdata $ myapp dashboard --data-dir=/tmp/e2e-run/flagdata → .../flagdata (flag wins) $ myapp dashboard --data-dir /tmp/e2e-run/flagdata2 → .../flagdata2 (space form) $ myapp --help | grep data-dir → --data-dir string override configured data dir ``` **Edge cases handled:** flag after subcommand; both spellings; `--` positional barrier; explicit-empty `--data-dir=` falls back to config (matches pflag zero-value semantics); missing value left to cobra's own error during `ExecuteContext`; repeated flags last-wins; the declared flag remains visible in `--help` and functionally parsed post-execution. ---
{"model": "deepseek-v4-flash", "problem_class": "go-cobra-dead-persistent-flag", "result": "passed", "tests": 5}