Description
PR #6058 (commit de900e0, closes #5932/#6018) wired 7 previously-dead subsystem validate() functions into Config::validate() (provider pool, STT, trajectory sentinel, gateway, utility scoring, memory fidelity, ACON thresholds). The commit message states the goal was to make these reachable "from the production config-load path."
The primary startup path is fixed: Bootstrap::new (src/bootstrap/mod.rs:202-203) calls Config::load(&config_path)? immediately followed by config.validate()?.
However, the hot-reload path never calls Config::validate() at all. Agent::reload_config (crates/zeph-core/src/agent/config_reload.rs:17) calls self.load_config_with_overlay(&path) (line 21), which itself only calls Config::load(path) (line 163) — no .validate() anywhere in either function. reload_config then applies dozens of fields straight from the unvalidated config onto the live Agent runtime (memory compression/routing thresholds, skill matching weights, context budget, ACON thresholds, etc.), and is invoked from the live config-watcher event handler at crates/zeph-core/src/agent/mod.rs:422.
Net effect: a config file edited while the agent is running (e.g. introducing an empty [[llm.providers]] pool, duplicate provider names, multiple default = true entries, inverted ACON/fidelity/trajectory thresholds) is silently applied at runtime with none of the 7 invariant checks the #6058 fix just wired in — the exact same "unreachable validate()" defect class the fix was meant to close, just at a second call site that PR missed.
Two smaller, lower-priority instances of the same gap exist in one-off CLI commands that load Config but never validate it: src/commands/worktree.rs:22 and src/commands/gonka.rs:385. These are lower severity since they only read subsystem fields (config.worktree.*) untouched by the 7 newly-wired validators, and gonka.rs's doctor-style diagnostics arguably wants granular per-check reporting rather than a hard validate() bail — worth a look but not blocking.
Reproduction Steps
- Start the agent with a valid config (e.g.
.local/config/testing.toml) and an active config-watcher (config_path set in runtime.lifecycle).
- While the agent is running, edit the config file on disk to set
memory.fidelity.full_threshold above compressed_threshold (or empty [[llm.providers]], or duplicate provider names).
- Trigger a reload (file-watch event fires
Agent::reload_config).
- Observe: the invalid config is applied to the live runtime with no validation error logged or surfaced.
Expected Behavior
reload_config/load_config_with_overlay should call Config::validate() on the freshly-loaded config before applying any field to the running agent, and reject the reload (keeping prior runtime state, matching the existing "keep previous state on error" pattern already used for overlay-merge failures) when validation fails.
Actual Behavior
No validation runs on the hot-reload path; invalid configs are applied directly to the live agent.
Environment
Logs / Evidence
crates/zeph-core/src/agent/config_reload.rs:17-23 (reload_config) and :159-197 (load_config_with_overlay) — no .validate() call in either.
crates/zeph-core/src/agent/mod.rs:422 — self.reload_config(); invoked from the config-watcher event handler.
- Contrast with
src/bootstrap/mod.rs:202-203 and src/tui_remote.rs:49-51, which both correctly call .validate() immediately after Config::load.
Description
PR #6058 (commit de900e0, closes #5932/#6018) wired 7 previously-dead subsystem
validate()functions intoConfig::validate()(provider pool, STT, trajectory sentinel, gateway, utility scoring, memory fidelity, ACON thresholds). The commit message states the goal was to make these reachable "from the production config-load path."The primary startup path is fixed:
Bootstrap::new(src/bootstrap/mod.rs:202-203) callsConfig::load(&config_path)?immediately followed byconfig.validate()?.However, the hot-reload path never calls
Config::validate()at all.Agent::reload_config(crates/zeph-core/src/agent/config_reload.rs:17) callsself.load_config_with_overlay(&path)(line 21), which itself only callsConfig::load(path)(line 163) — no.validate()anywhere in either function.reload_configthen applies dozens of fields straight from the unvalidated config onto the liveAgentruntime (memory compression/routing thresholds, skill matching weights, context budget, ACON thresholds, etc.), and is invoked from the live config-watcher event handler atcrates/zeph-core/src/agent/mod.rs:422.Net effect: a config file edited while the agent is running (e.g. introducing an empty
[[llm.providers]]pool, duplicate provider names, multipledefault = trueentries, inverted ACON/fidelity/trajectory thresholds) is silently applied at runtime with none of the 7 invariant checks the #6058 fix just wired in — the exact same "unreachable validate()" defect class the fix was meant to close, just at a second call site that PR missed.Two smaller, lower-priority instances of the same gap exist in one-off CLI commands that load
Configbut never validate it:src/commands/worktree.rs:22andsrc/commands/gonka.rs:385. These are lower severity since they only read subsystem fields (config.worktree.*) untouched by the 7 newly-wired validators, andgonka.rs's doctor-style diagnostics arguably wants granular per-check reporting rather than a hard validate() bail — worth a look but not blocking.Reproduction Steps
.local/config/testing.toml) and an active config-watcher (config_pathset inruntime.lifecycle).memory.fidelity.full_thresholdabovecompressed_threshold(or empty[[llm.providers]], or duplicate provider names).Agent::reload_config).Expected Behavior
reload_config/load_config_with_overlayshould callConfig::validate()on the freshly-loaded config before applying any field to the running agent, and reject the reload (keeping prior runtime state, matching the existing "keep previous state on error" pattern already used for overlay-merge failures) when validation fails.Actual Behavior
No validation runs on the hot-reload path; invalid configs are applied directly to the live agent.
Environment
Logs / Evidence
crates/zeph-core/src/agent/config_reload.rs:17-23(reload_config) and:159-197(load_config_with_overlay) — no.validate()call in either.crates/zeph-core/src/agent/mod.rs:422—self.reload_config();invoked from the config-watcher event handler.src/bootstrap/mod.rs:202-203andsrc/tui_remote.rs:49-51, which both correctly call.validate()immediately afterConfig::load.