atuinsh/atuin · error

empty config key

Error message

empty config key

What it means

In set_deep_key, the dotted key is split on '.' and the result is checked; a split of an empty/non-whitespace-validated string can still produce a single empty part. This specific bail is effectively unreachable because split() always yields at least one element (the callers validate non-empty keys first), but it documents the invariant that a key must yield real path segments.

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 c0c717ab04)

Solutions

  1. Ensure callers validate the key (non-empty, no whitespace) before calling set_deep_key, as config.rs run/get_updated_config already do.
  2. If extending the code, reuse the same trim/is_empty/whitespace check upstream of set_deep_key.
  3. Handle a key consisting only of dots (e.g. ".") which yields empty parts — add a per-part emptiness check if needed.

Example fix

// before
let parts: Vec<&str> = key.split('.').collect();
if parts.is_empty() { bail!("empty config key"); }
// after
let parts: Vec<&str> = key.split('.').collect();
if parts.iter().any(|p| p.is_empty()) { eyre::bail!("invalid dotted key: {key}"); }
Defensive patterns

Strategy: validation

Validate before calling

// caller-side guard before building dotted keys
fn ensure_dotted_key(key: &str) -> Result<(), String> {
    if key.split('.').any(|p| p.is_empty()) { return Err(format!("bad dotted key: {key}")); }
    Ok(())
}

Prevention

When it happens

Trigger: Directly reachable only if set_deep_key is called with a key that splits into zero parts — practically impossible via the CLI since `run`/`get_updated_config` trim and reject empty keys beforehand. Triggerable in principle by calling the internal function with an empty key.

Common situations: Not encountered by normal users; relevant only to contributors hacking on the config command internals or calling set_deep_key from new code paths without the upstream validation.

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@c0c717ab04 (2026-09-12). Data as JSON: /api/errors/2093b598c05661ce. Report an issue: GitHub.