Hmbown/CodeWhale · error

notification leaf has the wrong type or noncanonical value

Error message

notification leaf has the wrong type or noncanonical value

What it means

This error is thrown by edit_extras after parsing an existing notifications setting and re-reading its canonical value: if the round-tripped value does not equal the original config value, the leaf is deemed to have the wrong type or a noncanonical representation. It protects the config rewrite path from silently transforming values (e.g. a string where an integer is expected, or 1 vs "1").

Solutions

  1. Restore the leaf to the expected type and canonical form for that key (booleans as true/false, integers unquoted, paths as plain strings).
  2. Delete the malformed leaf and re-set it through the library's own update/apply path so it is written canonically.
  3. Regenerate the notifications section from a known-good default config.
  4. Check for recent hand edits or tool writes that changed the value's TOML type.

Example fix

// before
[notifications]
event_sound_min_interval_ms = "60000"  # string instead of integer
// after
[notifications]
event_sound_min_interval_ms = 60000
Defensive patterns

Strategy: validation

Validate before calling

// before calling edit_extras, ensure leaves have canonical TOML types:
// booleans unquoted, integers unquoted, paths as plain strings
fn leaf_matches(expected: &toml::Value, actual: &toml::Value) -> bool { expected == actual }

Try / catch

match edit_extras(extras, key, value) {
    Err(e) if e.to_string().contains("wrong type or noncanonical") => {
        eprintln!("notifications leaf '{key}' has wrong TOML type; fix or remove it");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling edit_extras with an extras table containing a notifications leaf whose TOML type or textual form does not survive NotificationConfigUpdate::parse(...).value() round-trip equality — e.g. wrong type for the key (string instead of integer/boolean), a value in noncanonical form, or a custom path represented differently than the canonical string form.

Common situations: A user hand-edited the notifications table and changed a leaf's type (quoted a boolean, stringified a number); an external tool wrote the config in a noncanonical style; a version upgrade changed the canonical representation of a value.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/87a6311cbad4005b. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/notifications.rs:442

    let key = key.to_ascii_lowercase();
    key == "notifications" || key.starts_with("notifications.")
}

pub fn edit_extras(
    extras: &mut std::collections::BTreeMap<String, toml::Value>,
    setting: NotificationSetting,
    value: Option<toml::Value>,
) -> Result<()> {
    if let Some(value) = &value {
        let parsed = match (setting, value) {
            (NotificationSetting::SoundFile, toml::Value::String(path)) => {
                let update = NotificationConfigUpdate::SoundFile(PathBuf::from(path));
                update.validate()?;
                update
            }
            _ => NotificationConfigUpdate::parse(setting, &display_value(Some(value.clone())))?,
        };
        anyhow::ensure!(
            parsed.value()?.as_ref() == Some(value),
            "notification leaf has the wrong type or noncanonical value"
        );
    }
    let mut updated = extras.clone();
    if value.is_some() || updated.contains_key("notifications") {
        let root = updated
            .entry("notifications".into())
            .or_insert_with(|| toml::Value::Table(toml::Table::new()));
        let mut table = root
            .as_table_mut()
            .context("notifications must be a TOML table")?;
        let segments = setting.key().split('.').collect::<Vec<_>>();
        let (key, parents) = segments.split_last().expect("notification key");
        let mut missing = false;
        for parent in parents {
            if value.is_none() && !table.contains_key(*parent) {
                missing = true;

View on GitHub (pinned to 73e0f67d83)