Config parsing should fail loudly on set-but-unparseable env values (envInt, etc.) #80
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Extends the fix on PR #78 (#63).
The config env helpers (
envInt, and any peers) silently fall back to the default when an env var is set but unparseable — the same silent-failure pattern @sneak flagged for the duration parser: a set-but-invalid config value should fail loudly and prevent startup, not be silently ignored.Definition of done:
envInt(and any other set-but-unparseable-swallowing helpers) return an error on a set-but-unparseable valueconfig.Newpropagates it so startup aborts on bad configNote: this is an intentional behaviour change — an existing deployment with a malformed config value will now fail to start rather than run with a silently-substituted default.
clawbot referenced this issue2026-08-07 18:52:09 +02:00
Implementation requirements
Baseline:
main@4f5ecb1. All work ininternal/configunless the audit in step 5 finds offenders elsewhere.Policy being encoded
A set-but-invalid environment value aborts startup. Defaults apply only to keys that are unset (or empty). There is no third behaviour — never silently substitute a default for a value the operator actually set.
1. Add
envPositiveIntAdd it verbatim as PR #87 defines it on
issue-64-receiver-rate-limit— same function name, same signatureenvPositiveInt(key string, defaultValue int) (int, error), same package-levelErrNonPositiveValue, same error strings:defaultValue, nil0, fmt.Errorf("invalid integer for %s: %q: %w", key, v, err)0, fmt.Errorf("%w: %s must be at least 1, got %q", ErrNonPositiveValue, key, v)Copying it byte-for-byte is deliberate: #87 is already merge-ready and will very likely land first, so an identical definition makes the rebase a trivial delete-one-copy instead of a semantic merge.
2.
envIntmust stop swallowingenvIntcurrently returnsdefaultValuewhen the value is set butstrconv.Atoifails. Its only caller isPORT. Fix by removingenvIntentirely and parsingPORTthroughenvPositiveInt, plus an explicit upper-bound check: a TCP port must be in1..65535. Add a package-levelErrInvalidPortfor the out-of-range case with an error naming the key and the bad value. If you keepenvIntfor any reason, it must return(int, error)— a silent-fallback variant must not survive anywhere in the package.3.
envBoolmust stop swallowingenvBoolcurrently treats every non-empty value that is nottrue/1asfalse, soDEBUG=yes,DEBUG=on, and a typo likeDEBUG=tureall silently disable debug. Change it toenvBool(key string, defaultValue bool) (bool, error):defaultValue, nilstrconv.ParseBool(stdlib, per our library policy — do not hand-roll a spelling table)Callers:
DEBUG,MAINTENANCE_MODE. Yes, this narrows the accepted set (mixed-case oddities likeTrUethatEqualFoldused to accept now fail) — that is the intended fail-loud behaviour, andstrconv.ParseBoolaccepts1 t T TRUE true True 0 f F FALSE false False.4.
envStringandenvDurationenvStringparses nothing and needs no change;envDurationis already fail-loud (#78). Leave both alone, but say explicitly in the PR body that they were audited, so the reviewer does not have to re-derive it.5. Audit beyond
internal/configGrep the whole repo for
os.Getenv/LookupEnvand check every parse site for the same silent-fallback pattern. Fix any you find under this issue, or state in the PR body that there are none outsideinternal/config. Do not leave the audit undone.6. Propagation
config.Newreturns the first error it hits so fx aborts startup. Errors must name the offending key and the offending value.7. Tests
Table-driven, one table per helper, three classes each: unset -> default, valid -> parsed, set-but-invalid -> error. Plus at least one
config.New-level test proving a badPORTand a badDEBUGeach abort with an error rather than returning a config. Uset.Setenvso nothing leaks between tests.8. Docs
README.md: state in the configuration section that any environment variable that is set but unparseable aborts startup, and that defaults apply only to unset variables. List the accepted boolean spellings.TODO.md: update in the same commit as the code.Definition of done
internal/configsilently falls back to a default for a set-but-invalid value.make checkgreen; validated via the repo's own entrypoints only (make check/script/cibuild) — never rawgo/golangci-lint..golangci.ymluntouched (org-standard, pinned onmainvia #86).(closes #80).Implementation plan
Branch
issue-80-config-fail-loudoffmain@4f5ecb1, single committitled with
(closes #80).internal/config/config.go: addenvPositiveInt(key string, defaultValue int) (int, error)byte-for-byte as PR #87 defines it,with the same package-level
ErrNonPositiveValueand the same errorstrings, so a rebase after #87 lands is a delete-one-copy operation.
envIntentirely.PORTis parsed through a newenvPortwrapper:
envPositiveIntfor the unset/unparseable/<1 cases plus anexplicit upper bound (
maxPort = 65535) returning a new package-levelErrInvalidPortnaming the key and the bad value.envBoolasenvBool(key string, defaultValue bool) (bool, error)usingstrconv.ParseBool; unset/empty keeps the default,anything else that fails to parse is a wrapped error naming the key
and value. Callers
DEBUGandMAINTENANCE_MODEupdated.envStringandenvDurationaudited and left alone (envStringparses nothing;
envDurationwas already made fail-loud in #78) —stated explicitly in the PR body.
os.Getenv/os.LookupEnvacross tracked files;result recorded in the PR body.
config.Newreturns the first error, so fx aborts startup. If theadded error handling pushes
Newpast thefunlenthreshold, theWEBHOOKER_ENVIRONMENTblock is extracted intoresolveEnvironment,again copied verbatim from #87 for the same rebase reason.
envPositiveInt,envPortand
envBool(unset -> default, valid -> parsed, set-but-invalid ->error) reaching the unexported helpers through an
export_test.goshim, plus
config.New-level tests proving a badPORTand a badDEBUGeach abort with an error instead of returning a config. Allenv manipulation via
t.Setenv.README.mdconfiguration section gains the fail-loud rule(defaults apply only to unset variables) and the list of accepted
boolean spellings;
TODO.mdupdated in the same commit;make fmtrun on the changed markdown.
Verification:
make fmtthenmake checkonly — no rawgoorgolangci-lintinvocations..golangci.ymluntouched.clawbot referenced this issue2026-08-09 04:33:24 +02:00
clawbot referenced this issue2026-08-11 14:48:08 +02:00