Problem
Five sub-config structs in src/core/config.rs don't have #[serde(default)]: TrackingConfig, DisplayConfig, FilterConfig, TelemetryConfig and LimitsConfig. When a TOML section omits any field, toml::from_str returns a missing field error. That error bubbles up to Config::load, and every helper that consumes the config (for example config::limits) calls .unwrap_or_default() on the Result, which silently swaps in the full default config and discards every user override in that section.
There's no feedback to the user. rtk config shows all defaults, so it looks like the file was parsed correctly when it wasn't.
Steps To Reproduce
# ~/.config/rtk/config.toml
[limits]
grep_max_results = 500
Expected: grep_max_results is 500, other limits stay at their documented defaults.
Actual: the whole [limits] section fails to parse (missing field grep_max_per_file), Config::load returns an error, and config::limits() silently falls back to LimitsConfig::default(). grep_max_results is 200.
Same pattern affects every other sub-config section: [tracking] requires history_days, [display] requires colors/emoji/max_width, [filters] requires both fields, and [telemetry] requires enabled. Omitting any of them breaks the whole section silently.
Impact
Users who override a single field in any section get no signal that their setting was ignored. The first time I noticed this was a partial [limits] section in my dotfiles that I assumed was working for months.
This looks like the bug that was reported in #1134 (closed as user confusion about [telemetry] vs [tracking]) and attempted in #1135 (self-closed for wrong base branch). The underlying parsing bug was never fixed.
Proposed Fix
Add struct-level #[serde(default)] to each of the five sub-config structs. They all already implement Default with the correct domain values, so the attribute just delegates to what's already there. No logic changes, no new defaults to maintain, backward compatible with fully specified configs.
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct LimitsConfig { ... }
Relevant Code
src/core/config.rs:34-126: the five sub-config struct definitions.
src/core/config.rs:128-130: config::limits() shows the silent-fallback pattern (Config::load().map(|c| c.limits).unwrap_or_default()).
HooksConfig at src/core/config.rs:27-31 already uses field-level #[serde(default)], so the pattern is established.
Co-Authored-By AI (Claude Opus 4.7) via Pi
Problem
Five sub-config structs in
src/core/config.rsdon't have#[serde(default)]:TrackingConfig,DisplayConfig,FilterConfig,TelemetryConfigandLimitsConfig. When a TOML section omits any field,toml::from_strreturns amissing fielderror. That error bubbles up toConfig::load, and every helper that consumes the config (for exampleconfig::limits) calls.unwrap_or_default()on theResult, which silently swaps in the full default config and discards every user override in that section.There's no feedback to the user.
rtk configshows all defaults, so it looks like the file was parsed correctly when it wasn't.Steps To Reproduce
Expected:
grep_max_resultsis500, other limits stay at their documented defaults.Actual: the whole
[limits]section fails to parse (missing field grep_max_per_file),Config::loadreturns an error, andconfig::limits()silently falls back toLimitsConfig::default().grep_max_resultsis200.Same pattern affects every other sub-config section:
[tracking]requireshistory_days,[display]requirescolors/emoji/max_width,[filters]requires both fields, and[telemetry]requiresenabled. Omitting any of them breaks the whole section silently.Impact
Users who override a single field in any section get no signal that their setting was ignored. The first time I noticed this was a partial
[limits]section in my dotfiles that I assumed was working for months.This looks like the bug that was reported in #1134 (closed as user confusion about
[telemetry]vs[tracking]) and attempted in #1135 (self-closed for wrong base branch). The underlying parsing bug was never fixed.Proposed Fix
Add struct-level
#[serde(default)]to each of the five sub-config structs. They all already implementDefaultwith the correct domain values, so the attribute just delegates to what's already there. No logic changes, no new defaults to maintain, backward compatible with fully specified configs.Relevant Code
src/core/config.rs:34-126: the five sub-config struct definitions.src/core/config.rs:128-130:config::limits()shows the silent-fallback pattern (Config::load().map(|c| c.limits).unwrap_or_default()).HooksConfigatsrc/core/config.rs:27-31already uses field-level#[serde(default)], so the pattern is established.Co-Authored-By AI (Claude Opus 4.7) via Pi