Hmbown/CodeWhale · error

refusing to mutate symlinked CodeWhale skills path component

Error message

refusing to mutate symlinked CodeWhale skills path component {}

What it means

checked_real_directory uses symlink_metadata so it can see a symlink before following it; this bail fires when a component of the CodeWhale-owned skills path (anchor, .codewhale, or skills, or the target itself) is a symlink. Mutating through a symlink would redirect writes to an attacker- or user-selected location outside the owned root, so the mutation is refused. The symlinked path component is the input at fault; this is a generic guard shared by all owned-root mutation helpers.

Source

Thrown at crates/tui/src/skills/mutation.rs:149

fn owned_anchor<'a>(
    workspace: &'a Path,
    home: Option<&'a Path>,
    target: SkillTargetScope,
) -> Result<&'a Path> {
    match target {
        SkillTargetScope::Project => Ok(workspace),
        SkillTargetScope::Global => home.context("global skill mutations require a home directory"),
    }
}

/// Return whether `path` is an existing real directory, rejecting links and
/// non-directory components. `symlink_metadata` is intentional: following a
/// link before checking it would turn a lexical CodeWhale-owned root into an
/// attacker-selected write target.
fn checked_real_directory(path: &Path) -> Result<bool> {
    match fs::symlink_metadata(path) {
        Ok(meta) if meta.file_type().is_symlink() => {
            bail!(
                "refusing to mutate symlinked CodeWhale skills path component {}",
                path.display()
            )
        }
        Ok(meta) if !meta.is_dir() => bail!(
            "refusing to mutate through non-directory CodeWhale skills path component {}",
            path.display()
        ),
        Ok(_) => Ok(true),
        Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
        Err(err) => Err(err).with_context(|| format!("failed to inspect {}", path.display())),
    }
}

/// Validate the complete owned-root chain without following a symlink in the
/// workspace/home anchor, `.codewhale`, or `skills` component.
fn validate_owned_target_chain(
    anchor: &Path,

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Remove the symlink and replace it with a real directory at that component
  2. Re-create the skills directory (rm the link, then let CodeWhale create a real .codewhale/skills tree)
  3. Do not point workspace/home paths for skills through links
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at crates/tui/src/skills/mutation.rs:149 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/6c30a8fd70a5bfaf. Report an issue: GitHub.