Hmbown/CodeWhale · error

Failed to lock settings

Error message

Failed to lock settings: {} has no parent directory

What it means

with_settings_file_lock guards settings writes with a lockfile adjacent to the settings path. Before creating the lock it takes path.parent(); if the path has no parent component (e.g. a bare relative filename like "settings.toml" resolving to "") or the parent is the empty OS string, it bails instead of attempting to create a directory.

Solutions

  1. Pass an absolute settings path that includes a parent directory, e.g. ~/.config/codewhale/settings.toml.
  2. Fix the misconfigured config-dir source (env var, CLI flag) so it is not empty — this codebase fails loud on misconfiguration by design.
  3. In tests, use a tempdir path like std::env::temp_dir().join("settings.toml").

Example fix

// before
with_settings_file_lock(Path::new("settings.toml"), || settings.save())?;
// after
let path = dirs::config_dir().unwrap().join("codewhale/settings.toml");
with_settings_file_lock(&path, || settings.save())?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_parent(path: &std::path::Path) -> bool {
    path.parent().map_or(false, |p| !p.as_os_str().is_empty())
}

Try / catch

match with_settings_file_lock(&path, || save_settings()) {
    Err(e) if e.to_string().contains("has no parent directory") => {
        eprintln!("config dir unset; pass --config-dir or set the env var");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling the settings load/save path with a PathBuf that has no parent — e.g. with_settings_file_lock(Path::new("settings.toml"), ..), or constructing the settings path from an empty/unset config-dir variable so the resolved path is a bare filename.

Common situations: Environment misconfiguration where the config directory env/home lookup returns empty, unit tests passing a bare filename, or custom config-dir flags set to "".

Related errors


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

Appendix: source

Thrown at crates/tui/src/settings.rs:2311

) -> Result<T> {
    let path = Settings::path()?;
    let _process_guard = lock_settings_transaction(settings_transaction_mutex(&path));
    with_settings_file_lock(&path, || {
        operation(&SettingsTransaction { path: path.clone() })
    })
}

/// Hold an exclusive advisory lock on `<settings.toml>.lock` for `operation`.
///
/// The lock file is opened (not followed) with owner-only permissions and is
/// created if absent. Dropping the `fd_lock` guard — including on an unwind —
/// releases it, and the OS releases it if the process dies, so a crash cannot
/// wedge another Codewhale instance out of its settings.
fn with_settings_file_lock<T>(path: &Path, operation: impl FnOnce() -> Result<T>) -> Result<T> {
    use std::fs;

    let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) else {
        anyhow::bail!(
            "Failed to lock settings: {} has no parent directory",
            path.display()
        );
    };
    fs::create_dir_all(parent)
        .with_context(|| format!("Failed to create config directory {}", parent.display()))?;

    let mut lock_name = path
        .file_name()
        .context("Failed to lock settings: settings path has no file name")?
        .to_os_string();
    lock_name.push(".lock");
    let lock_path = parent.join(lock_name);
    reject_settings_lock_symlink(&lock_path)?;

    let mut options = fs::OpenOptions::new();
    options.read(true).write(true).create(true);
    #[cfg(unix)]

View on GitHub (pinned to 73e0f67d83)