Hmbown/CodeWhale · error
Refusing a symlinked settings lock at
Error message
Refusing a symlinked settings lock at {} What it means
Before locking, the code checks the lock path with symlink_metadata and refuses to proceed if it is a symlink. A planted `settings.toml.lock -> /some/target` would let an attacker choose which file the app creates with its own permissions, so locking through symlinks is rejected outright. This is a deliberate hardening measure, not a transient failure.
Solutions
- Replace the symlink with a real regular file: `rm <settings.toml.lock> && touch <settings.toml.lock>`
- Reconfigure your dotfile manager to copy rather than symlink this specific file
- Retry the settings operation to recreate a fresh regular lock file
Example fix
// before (shell) ls -la settings.toml.lock # settings.toml.lock -> /etc/passwd // after (shell) rm settings.toml.lock && touch settings.toml.lock
Defensive patterns
Strategy: validation
Validate before calling
fn lock_is_symlink(p: &std::path::Path) -> bool {
std::fs::symlink_metadata(p).map(|m| m.file_type().is_symlink()).unwrap_or(false)
}
// if true, replace with a real file before calling the library Try / catch
if lock_is_symlink(&lock_path) {
std::fs::remove_file(&lock_path).ok();
std::fs::File::create(&lock_path).ok();
}
let lock = acquire_settings_lock()?; Prevention
- Configure dotfile managers (stow, dotbot) to copy, not symlink, this file
- Don't plant symlinks in config directories
- Run with least privilege so a planted symlink can't redirect file creation
When it happens
Trigger: Any settings lock acquisition when `settings.toml.lock` is a symlink (e.g. pointing elsewhere on disk), detected via std::fs::symlink_metadata in reject_settings_lock_symlink.
Common situations: Dotfile managers that symlink config directories wholesale (stow, homesick, dotbot); an attacker planting a symlink to escalate file creation; sync tools that replaced the lock with a link.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- built-in plugin path may not be a symbolic link or reparse…
- CodewhalePet/1
- could not securely open
- external credential path must name a non-reparse regular…
- file is not a regular single-link file
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/b671f7b1f9d81766.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/settings.rs:2375
{
anyhow::bail!(
"Refusing a non-regular settings lock at {}",
lock_path.display()
);
}
let mut lock = fd_lock::RwLock::new(lock_file);
let _guard = lock
.write()
.with_context(|| format!("Failed to acquire settings lock at {}", lock_path.display()))?;
operation()
}
/// Refuse to lock through a symlink: a planted `settings.toml.lock -> …` would
/// otherwise let an attacker pick which file we create with our permissions.
fn reject_settings_lock_symlink(lock_path: &Path) -> Result<()> {
match std::fs::symlink_metadata(lock_path) {
Ok(metadata) if metadata.file_type().is_symlink() => anyhow::bail!(
"Refusing a symlinked settings lock at {}",
lock_path.display()
),
Ok(_) | Err(_) => Ok(()),
}
}
/// Replace `path` with `body` by writing an adjacent temporary file and
/// renaming it into place.
///
/// A direct `fs::write` truncates first, so any concurrent reader — another
/// Codewhale process, an editor, a `cat` — can observe a half-written file and
/// parse it as truncated TOML, silently losing every key past the tear. A
/// same-directory temp file plus the platform's replace primitive makes the
/// swap atomic for readers: they see either the whole previous file or the
/// whole new one.
///
/// The temp file inherits the existing file's permission bits when there is oneView on GitHub (pinned to 73e0f67d83)