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

  1. Remove the symlink at the lock path and let the application recreate a regular lock file
  2. Restore the lock path as a regular file from a known-good backup
  3. Check who/what created the symlink — on a shared machine treat it as potential tampering
  4. 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

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


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)