Skip to content

fix(config): make every config key environment-overridable - #98

Merged
jamesainslie merged 1 commit into
mainfrom
fix-env-override-coverage
Aug 12, 2026
Merged

jamesainslie merged 1 commit into
mainfrom
fix-env-override-coverage

Conversation

@jamesainslie

Copy link
Copy Markdown
Collaborator

Summary

Fixes #86.

DOT_ env overrides were driven by four hand-lists that had to agree (the BindEnv whitelist, the per-section env readers, the zero-value merge functions, and the struct itself) and did not:

  • Keys outside the lists were silently ignored: dotfile.package_name_mapping (the reported key), symlinks.backup_dir, output.table_style, and the entire update and network sections.
  • The sparse-overlay merge only propagated non-zero values, so an env boolean set to false could never override a file value of true. DOT_SYMLINKS_FOLDING=false and DOT_DOTFILE_TRANSLATE=false were also silent no-ops even though they were bound and read.

Changes

  • Env bindings derive from the config struct via reflection (tolerant of untagged or non-struct fields), so a new key cannot ship unbound.
  • Overrides apply directly onto the loaded config under viper IsSet, replacing the sparse-config merge; explicit false now survives. The env-only mergeConfigs path is deleted; the flags path is untouched (its residual issues are filed as Loader.LoadWithFlags is production-dead and its flag merge cannot represent explicit false #97).
  • Path-typed env values expand ~/$VAR through the same ordered pathFields list ExpandPaths uses (shared list, deterministic error attribution).
  • List env values are comma-separated, matching the documented contract and the config set convention; viper alone would whitespace-split, turning the documented DOT_IGNORE_PATTERNS="*.log,*.tmp" example into one bogus pattern.
  • Docs updated: the stale partial binding list in 04-configuration.md and the "not every key is bound" caveat in 08-troubleshooting.md now state the uniform rule.

Tests

  • TestLoader_EnvOverridesEveryConfigKey: reflection walk over every struct key, one valid non-default sample each (vacuous samples rejected), proving each env form lands. Mutation-checked: removing keys from the apply maps fails exactly those subtests.
  • TestLoader_EnvFalseOverridesFileTrue: the exact issue repro shape.
  • TestLoader_EnvListValuesAreCommaSeparated.

Verified end to end with a built binary: DOT_DOTFILE_PACKAGE_NAME_MAPPING=false now overrides both the default and an explicit file true. Full suite green with race and coverage; golangci-lint clean.

DOT_ env overrides were driven by four hand-lists that had to agree (the
BindEnv whitelist, the per-section env readers, the zero-value merge
functions, and the struct itself) and did not: unlisted keys such as
dotfile.package_name_mapping, symlinks.backup_dir, output.table_style,
and the whole update and network sections were silently ignored, and the
sparse-overlay merge could never propagate an env boolean set to false
over a file value of true.

Environment bindings are now derived by reflection from the config
struct's mapstructure tags, and overrides are applied directly onto the
loaded configuration under viper's IsSet, so explicit false survives.
Path-typed values expand tilde and $VAR at apply time through the same
ordered field list ExpandPaths uses. List values are comma-separated,
matching the documented contract and the config set convention, where
viper's own env handling would whitespace-split.

A reflection-driven test walks every struct key and proves its env form
lands, with a guard against vacuous samples, so a future key cannot ship
silently unbound. Docs that enumerated the old partial binding list now
state the uniform rule.

Fixes #86
@jamesainslie
jamesainslie merged commit 4fba642 into main Aug 12, 2026
10 checks passed
@jamesainslie
jamesainslie deleted the fix-env-override-coverage branch August 12, 2026 17:42
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.

Env overrides silently ignored for unbound config keys (DOT_DOTFILE_PACKAGE_NAME_MAPPING)

1 participant