go-viper-config-key-case
I diagnosed the bug against the real repo, reproduced it on the pre-fix revision, and verified the fix on 749386c. The full write-up is at ~/DF-BUNKER-1-solution.md.
Root cause. internal/cli/config.go used Viper for both save and load:
- viper.WriteConfigAs preserved map-key case on disk (confirmed: the file contains karaHermes-mde-7840hs: verbatim).
- viper.Unmarshal(cfg) lowercases every map key, but active_server is a scalar so it keeps original case. Result: cfg.Servers["karahermes-..."] vs cfg.ActiveServer == "karaHermes-...".
- Every command does cfg.Servers[cfg.ActiveServer], so all post-connect commands hit "not found in config". Legacy files self-heal because the corruption was read-only.
Fix (commit 749386c). Replace only the config I/O with direct go.yaml.in/yaml/v3:
- LoadCLIConfig: os.ReadFile + yaml.Unmarshal, with os.IsNotExist handling.
- SaveCLIConfig: yaml.Marshal + os.WriteFile(cfgPath, data, 0o600).
- Promote go.yaml.in/yaml/v3 from indirect to direct in go.mod.
- Viper is retained for env/flag token binding (connect.go, client.go).
Regression tests added in internal/cli/config_test.go deliberately use mixed-case keys (karaHermes-mde-7840hs): round-trip, legacy-heal, and lowercase-preserved.
Verification performed:
- Pre-fix reproduction (749386c^): ActiveServer = "karaHermes-mde-7840hs", Servers key = "karahermes-mde-7840hs" → test FAILS (bug reproduced).
- Post-fix: all three targeted tests PASS; full go test ./internal/cli/ → ok; go build ./... → exit 0.
- On-disk YAML after fix retains original case.
Apply with:
git show 749386c -- internal/cli/config.go go.mod | git apply
git show 749386c -- internal/cli/config_test.go | git apply
# Evidence - Problem class: go-viper-config-key-case - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-14T01:07:09.188Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "bunker DF-BUNKER-1: cobra+viper CLI config round-trip lowercased servers map keys on Unmarshal while active_server string kept original case \u2014 every post-connect command failed \"not found in config\". Fix: replace viper with direct go.yaml.in/yaml/v3 I/O (struct already yaml-tagged) in the CLI config layer only; viper kept for env/flag token binding. Legacy on-disk configs self-heal because viper write path preserved case; only reads broke. Regression tests must use mixed-case keys. Commit 749386c in deployBunker/bunker.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-viper-config-key-case", "provider": "openrouter", "solved_at": "2026-09-14T01:07:09.188Z", "version": ""}I diagnosed the bug against the real repo, reproduced it on the pre-fix revision, and verified the fix on 749386c. The full write-up is at ~/DF-BUNKER-1-solution.md.
Root cause. internal/cli/config.go used Viper for both save and load:
- viper.WriteConfigAs preserved map-key case on disk (confirmed: the file contains karaHermes-mde-7840hs: verbatim).
- viper.Unmarshal(cfg) lowercases every map key, but active_server is a scalar so it keeps original case. Result: cfg.Servers["karahermes-..."] vs cfg.ActiveServer == "karaHermes-...".
- Every command does cfg.Servers[cfg.ActiveServer], so all post-connect commands hit "not found in config". Legacy files self-heal because the corruption was read-only.
Fix (commit 749386c). Replace only the config I/O with direct go.yaml.in/yaml/v3:
- LoadCLIConfig: os.ReadFile + yaml.Unmarshal, with os.IsNotExist handling.
- SaveCLIConfig: yaml.Marshal + os.WriteFile(cfgPath, data, 0o600).
- Promote go.yaml.in/yaml/v3 from indirect to direct in go.mod.
- Viper is retained for env/flag token binding (connect.go, client.go).
Regression tests added in internal/cli/config_test.go deliberately use mixed-case keys (karaHermes-mde-7840hs): round-trip, legacy-heal, and lowercase-preserved.
Verification performed:
- Pre-fix reproduction (749386c^): ActiveServer = "karaHermes-mde-7840hs", Servers key = "karahermes-mde-7840hs" → test FAILS (bug reproduced).
- Post-fix: all three targeted tests PASS; full go test ./internal/cli/ → ok; go build ./... → exit 0.
- On-disk YAML after fix retains original case.
Apply with:
git show 749386c -- internal/cli/config.go go.mod | git apply
git show 749386c -- internal/cli/config_test.go | git apply
# Evidence - Problem class: go-viper-config-key-case - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-14T01:07:09.188Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "bunker DF-BUNKER-1: cobra+viper CLI config round-trip lowercased servers map keys on Unmarshal while active_server string kept original case \u2014 every post-connect command failed \"not found in config\". Fix: replace viper with direct go.yaml.in/yaml/v3 I/O (struct already yaml-tagged) in the CLI config layer only; viper kept for env/flag token binding. Legacy on-disk configs self-heal because viper write path preserved case; only reads broke. Regression tests must use mixed-case keys. Commit 749386c in deployBunker/bunker.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-viper-config-key-case", "provider": "openrouter", "solved_at": "2026-09-14T01:07:09.188Z", "version": ""}