✨ feat(config): hot-reload Phase 1 — logging.level (ADR-0023) #42
Reference in New Issue
Block a user
Delete Branch "feat/adr-0023-config-hot-reload-mvp"
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?
Stretch sprint of autonomous trainer day 2026-05-05
Implements Phase 1 of ADR-0023 selective hot-reloading. The infrastructure (
viper.WatchConfig+OnConfigChangecallback + thread-safe re-unmarshal) is now in place. The first field wired through it islogging.level.Remaining fields listed in ADR-0023 (
api.v2_enabled, telemetry sampler,auth.jwt.ttl) follow the same pattern and will land in subsequent phase PRs without further infrastructure work.Why a phased rollout
Hot-reloading any field touches a different runtime invariant:
logging.level— global zerolog state, single setter, no concurrency hazard ✓api.v2_enabled— request routing, must coordinate with the chi router rebuildauth.jwt.ttl— affects JWT validation in-flight; needs careful invariantShipping the foundation + one trivial field gives us a vetted infrastructure to extend, rather than landing 4 fields together with 4× risk.
Changes
pkg/config/config.goConfigstruct gets unexportedviper *viper.ViperandreloadMu sync.RWMutexfields (withmapstructure:"-").WatchAndApply(ctx):config.yamlare safe.OnConfigChangeto re-unmarshal under the mutex, then callSetupLogging().viper.WatchConfig().ctxis cancelled.pkg/server/server.go Run()s.config.WatchAndApply(rootCtx)after the JWT cleanup loop start so it stops on graceful shutdown.Tests (
pkg/config/config_hot_reload_test.go, new)TestWatchAndApply_LoggingLevel— end-to-end: write file → watcher fires → in-memory level flips. Polls up to 2s.TestWatchAndApply_NoFileNoOp— no config file in use → no panic.TestWatchAndApply_NilViperNoOp—Config{}constructed manually → no panic.go test -race ./pkg/config/...passes.Out of scope
features/config/config_hot_reloading.feature(@flakyscenarios) — file-watching cross-process semantics are sensitive to filesystem behaviour on the CI runner; the unit test exercises the same code path deterministically. Will revisit when activating the next field.Verifier verdict (skill-driven, manual run)
APPROVE
WatchAndApplyis 35 lines, mostly defensive checks + the OnConfigChange closure; race detector clean; mutex used correctly.pkg/config/config.go,pkg/server/server.go Run) match the actual file locations.Test plan
go test -race ./pkg/config/...passesgo vet ./...clean./bin/serverrunning, editconfig.yamllogging.level→ check level changes in logs without restart