{"record":{"id":"2093b598c05661ce","repo":"atuinsh/atuin","slug":"empty-config-key","errorCode":null,"errorMessage":"empty config key","messagePattern":"empty config key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin/src/command/client/config.rs","lineNumber":323,"sourceCode":"\n    if v.is_str() {\n        Some(ValueType::String)\n    } else if v.is_bool() {\n        Some(ValueType::Boolean)\n    } else if v.is_integer() {\n        Some(ValueType::Integer)\n    } else if v.is_float() {\n        Some(ValueType::Float)\n    } else {\n        None\n    }\n}\n\nfn set_deep_key(doc: &mut DocumentMut, key: &str, value: Value) -> Result<()> {\n    let parts: Vec<&str> = key.split('.').collect();\n\n    if parts.is_empty() {\n        eyre::bail!(\"empty config key\");\n    }\n\n    let mut current: &mut dyn TableLike = doc.as_table_mut();\n\n    // Navigate/create intermediate tables\n    for &part in &parts[..parts.len() - 1] {\n        if !current.contains_key(part) {\n            current.insert(part, Item::Table(Table::new()));\n        }\n        current = current\n            .get_mut(part)\n            .expect(\"just inserted or already exists\")\n            .as_table_like_mut()\n            .ok_or_else(|| eyre::eyre!(\"'{}' exists but is not a table\", part))?;\n    }\n\n    let last = *parts.last().unwrap();\n","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/atuinsh/atuin/blob/c0c717ab04c881764bcad4b3d169a507e2432643/crates/atuin/src/command/client/config.rs#L305-L341","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure callers validate the key (non-empty, no whitespace) before calling set_deep_key, as config.rs run/get_updated_config already do.","If extending the code, reuse the same trim/is_empty/whitespace check upstream of set_deep_key.","Handle a key consisting only of dots (e.g. \".\") which yields empty parts — add a per-part emptiness check if needed."],"exampleFix":"// before\nlet parts: Vec<&str> = key.split('.').collect();\nif parts.is_empty() { bail!(\"empty config key\"); }\n// after\nlet parts: Vec<&str> = key.split('.').collect();\nif parts.iter().any(|p| p.is_empty()) { eyre::bail!(\"invalid dotted key: {key}\"); }","handlingStrategy":"validation","validationCode":"// caller-side guard before building dotted keys\nfn ensure_dotted_key(key: &str) -> Result<(), String> {\n    if key.split('.').any(|p| p.is_empty()) { return Err(format!(\"bad dotted key: {key}\")); }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never construct dotted keys by naive concatenation that can yield empty segments.","Validate keys at CLI entry, as config.rs does.","Add tests covering keys like \".\" and \"a..b\" if extending config internals."],"tags":["config","internal-invariant","toml"],"backgroundTag":"empty-required-field","analyzedSha":"c0c717ab04c881764bcad4b3d169a507e2432643","analyzedAt":"2026-09-12T07:40:01.341Z","contentChangedAt":"2026-09-12T07:40:01.341Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}