Hmbown/CodeWhale · error
sub-agent state path must not traverse symlinks: {}
Error message
sub-agent state path must not traverse symlinks: {} What it means
Walking the path component by component from the state root, `reject_root_relative_symlinks` found an existing component that is a symlink. The guard refuses to traverse any symlink inside a state path so a tampered link cannot redirect reads or writes outside the root, even when the final target looks contained.
Source
Thrown at crates/tui/src/tools/subagent/mod.rs:7747
normalized
}
}
fn reject_root_relative_symlinks(root: &Path, path: &Path) -> Result<()> {
let relative = path.strip_prefix(root).map_err(|_| {
anyhow!(
"sub-agent state path must stay within state root: {}",
path.display()
)
})?;
let mut current = root.to_path_buf();
for component in relative.components() {
current.push(component.as_os_str());
let Ok(metadata) = fs::symlink_metadata(¤t) else {
continue;
};
if metadata.file_type().is_symlink() {
return Err(anyhow!(
"sub-agent state path must not traverse symlinks: {}",
current.display()
));
}
}
Ok(())
}
fn read_subagent_state_file(state_root: &Path, path: &Path) -> Result<String> {
let state_root = normalize_subagent_workspace(state_root);
reject_root_relative_symlinks(&state_root, path)?;
let metadata = fs::symlink_metadata(path)?;
let file_type = metadata.file_type();
if file_type.is_symlink() || !file_type.is_file() {
return Err(anyhow!(
"sub-agent state path must be a regular file: {}",
path.display()
));View on GitHub (pinned to 0c42157ee5)
Solutions
- Find the offending component (the error names the exact path) and replace the symlink with a real directory.
- Move the state root to a path with no symlinked components (e.g. a direct path under ~/.local/share or an explicit non-linked dir).
- When unpacking state archives, use flags that don't preserve symlinks for internal components.
Example fix
# before: state root contains a symlink ln -s /tmp/escape ~/.codewhale/agents # -> Err 1214 on next artifact read # after rm ~/.codewhale/agents && mkdir ~/.codewhale/agents
Defensive patterns
Strategy: validation
Validate before calling
fn path_traverses_symlink(root: &Path, rel: &Path) -> bool {
let mut cur = root.to_path_buf();
for c in rel.components() {
cur.push(c.as_os_str());
if let Ok(m) = std::fs::symlink_metadata(&cur) {
if m.file_type().is_symlink() { return true; }
}
}
false
} Type guard
fn is_symlink_free(root: &Path, rel: &Path) -> bool {
!path_traverses_symlink(root, rel)
} Try / catch
match read_subagent_state_file(&state_root, &path) {
Err(e) if e.to_string().contains("must not traverse symlinks") => { /* error names the component: replace it with a real dir */ }
r => r?,
} Prevention
- Keep the entire state tree symlink-free; use real directories.
- Avoid placing state under symlinked home/workspace dirs.
- Unpack state archives without preserving internal symlinks.
When it happens
Trigger: Any directory component of the state path replaced by a symlink: an attacker- or child-created link inside the state dir, a symlinked projects folder, or sync tools (Dropbox-style) materializing links.
Common situations: State directory inside a symlinked home or workspace; symlink-based version switching of state dirs; adversarial model output planting links; restoring state from archives that preserve links.
Related errors
- sub-agent transcript artifact is not a regular file: {}
- sub-agent state path must stay within state root: {}
- sub-agent state path must be a regular file: {}
- persistent path allow rules must stay within the workspace
- config path cannot contain '..' components
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/5a82ee2297408c84.
Report an issue: GitHub.