Hmbown/CodeWhale · error
refusing a symlinked Runtime Chat owner lock
Error message
refusing a symlinked Runtime Chat owner lock
What it means
RelayScopeLock::acquire refuses to open the Runtime Chat owner lock file if the filesystem path is a symlink. Symlinked lock files are a classic privilege-escalation vector (an attacker points the lock path at a sensitive file the process would then open read/write), so the code checks symlink_metadata first and bails.
Solutions
- Remove the symlink at the lock path and let the application recreate a regular lock file
- Restore the lock path as a regular file from a known-good backup
- Check who/what created the symlink — on a shared machine treat it as potential tampering
- Run the app against a state directory that is not managed by symlink-based dotfile tooling
Example fix
// before ln -s /etc/passwd ~/.local/state/codewhale/runtime-chat-owner.lock // after rm ~/.local/state/codewhale/runtime-chat-owner.lock # let the app recreate it as a regular file
Defensive patterns
Strategy: validation
Validate before calling
fn lock_path_is_safe(path: &std::path::Path) -> bool {
!matches!(std::fs::symlink_metadata(path), Ok(m) if m.file_type().is_symlink())
} Try / catch
match RelayScopeLock::acquire(&lock_path) {
Err(e) if e.to_string().contains("symlinked") => {
eprintln!("lock path is a symlink; removing and retrying with a clean path");
std::fs::remove_file(&lock_path)?;
RelayScopeLock::acquire(&lock_path)?
}
other => other?,
} Prevention
- Keep state directories owned by the running user and not world-writable
- Do not symlink individual state files (dotfile managers should manage directories, not lock files)
- After backup restores, verify lock paths are regular files
When it happens
Trigger: Acquiring the owner scope lock when something (another process, a user, an attacker) has replaced the expected regular lock file at the state path with a symlink pointing elsewhere.
Common situations: Tampered or shared state directories (e.g. world-writable tmp dirs); restoring state via a symlink farm or dotfile manager that symlinks files; copying state with tools that create symlinks instead of copies.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Automation lock must not be a reparse point
- Automation lock must not have hard links
- built-in plugin path may not be a symbolic link or reparse…
- CodewhalePet/1
- could not securely open
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/4014805507a24561.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_chat_relay.rs:1564
.into_owned(),
);
execution.context.project_pack = Some(false);
execution
.skills
.get_or_insert_with(SkillsConfig::default)
.scan_codewhale_only = Some(true);
Ok((execution, workspace))
}
#[derive(Debug)]
struct RelayScopeLock {
_file: File,
}
impl RelayScopeLock {
fn acquire(path: &Path) -> Result<Self> {
if fs::symlink_metadata(path).is_ok_and(|metadata| metadata.file_type().is_symlink()) {
bail!("refusing a symlinked Runtime Chat owner lock");
}
let mut options = fs::OpenOptions::new();
options.read(true).write(true).create(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.mode(0o600).custom_flags(libc::O_NOFOLLOW);
}
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt as _;
use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
options.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);
}
let file = options.open(path).context("open Runtime Chat owner lock")?;
if !file
.metadata()
.context("inspect Runtime Chat owner lock")?View on GitHub (pinned to 73e0f67d83)