atuinsh/atuin · error

empty config key

Error message

empty config key

What it means

`set_deep_key` splits the config key on `.` and asserts at least one part exists before walking/creating nested TOML tables. Because `String::split('.')` on an empty string actually yields one empty part, this bail is effectively a defensive invariant guard for an empty/degenerate key reaching the function.

Solutions

  1. Always pass a non-empty key with at least one non-empty segment: `atuin config set theme.name dark`.
  2. Validate/trim the key before calling config-set logic.
  3. Prefer single-level keys like `atuin config set auto_sync true` when no nesting is needed.

Example fix

// before
set_deep_key(doc, "", value)  // bails: empty config key
// after
set_deep_key(doc, "theme.name", value)
Defensive patterns

Strategy: validation

Validate before calling

fn valid_key(k: &str) -> bool {
    !k.is_empty() && k.split('.').all(|p| !p.is_empty())
}

Type guard

fn valid_key(k: &str) -> bool {
    !k.is_empty() && k.split('.').all(|p| !p.is_empty())
}

Prevention

When it happens

Trigger: Calling `set_deep_key` with an empty or all-dot key path (e.g. "" or "."), reached via `config set` internal paths or `get_updated_config` when the key argument degenerates to nothing.

Common situations: Programmatic/plugin use of the set path with a malformed key; edge cases where a key consisting only of dots produces only empty parts.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19). Data as JSON: /api/errors/2093b598c05661ce. Report an issue: GitHub.

Appendix: source

Thrown at crates/atuin/src/command/client/config.rs:323

    if v.is_str() {
        Some(ValueType::String)
    } else if v.is_bool() {
        Some(ValueType::Boolean)
    } else if v.is_integer() {
        Some(ValueType::Integer)
    } else if v.is_float() {
        Some(ValueType::Float)
    } else {
        None
    }
}

fn set_deep_key(doc: &mut DocumentMut, key: &str, value: Value) -> Result<()> {
    let parts: Vec<&str> = key.split('.').collect();

    if parts.is_empty() {
        eyre::bail!("empty config key");
    }

    let mut current: &mut dyn TableLike = doc.as_table_mut();

    // Navigate/create intermediate tables
    for &part in &parts[..parts.len() - 1] {
        if !current.contains_key(part) {
            current.insert(part, Item::Table(Table::new()));
        }
        current = current
            .get_mut(part)
            .expect("just inserted or already exists")
            .as_table_like_mut()
            .ok_or_else(|| eyre::eyre!("'{}' exists but is not a table", part))?;
    }

    let last = *parts.last().unwrap();

View on GitHub (pinned to 900007c480)