atuinsh/atuin · error

' ' is a table; use a dotted key like ' .key' to set a…

Error message

'{}' is a table; use a dotted key like '{}.key' to set a value within it

What it means

When setting a deep config key, `set_deep_key` refuses to overwrite an existing TOML table (or inline table) with a scalar value. This prevents silently destroying a whole config section — e.g. replacing the entire `[daemon]` table with `daemon = true`.

Solutions

  1. Set a leaf key inside the table instead: `atuin config set daemon.enabled true`.
  2. Inspect the existing structure first (`atuin config` or read config.toml) to find the correct dotted path.
  3. If you truly want to replace the table, edit config.toml by hand.
  4. Re-check the intended config schema — the value you are setting may correspond to a nested option.

Example fix

// before
atuin config set daemon true
// after
atuin config set daemon.enabled true
Defensive patterns

Strategy: validation

Validate before calling

# check whether the target key is a table before setting
atuin config get daemon  # if it prints a section, set leaf keys instead
atuin config set daemon.enabled true

Prevention

When it happens

Trigger: Running `atuin config set daemon true`, `atuin config set sync true`, or any `set` whose final key segment currently names a table in config.toml while the new value is a scalar.

Common situations: Trying to toggle a section rather than a leaf setting; not realizing a key like `daemon` groups sub-options (`daemon.enabled`, `daemon.systemd_socket`); typos where a leaf key was intended.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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

Appendix: source

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

    // 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();

    // Don't silently overwrite a table with a scalar value
    if let Some(existing) = current.get(last)
        && (existing.is_table() || existing.is_inline_table())
    {
        eyre::bail!(
            "'{}' is a table; use a dotted key like '{}.key' to set a value within it",
            key,
            key
        );
    }

    if let Some(item) = current.get_mut(last) {
        let mut value = value;
        if let Some(old_value) = item.as_value_mut() {
            // Preserve any commands attached to the old value.
            std::mem::swap(value.decor_mut(), old_value.decor_mut());
        }
        *item = Item::Value(value);
    } else {
        current.insert(last, Item::Value(value));
    }

    Ok(())

View on GitHub (pinned to 900007c480)