langchain-ai/deepagents · error · SystemExit
Error: Skill directory is a symlink. Refusing to delete for
Error message
Error: Skill directory is a symlink. Refusing to delete for safety.
What it means
Just before performing the removal (after confirmation, allowing for the pause at the prompt), `_delete` re-checks `skill_dir.is_symlink()`. A symlinked skill directory could delete the target directory's contents elsewhere on disk, so the command hard-refuses as a safety measure.
Source
Thrown at libs/code/deepagents_code/skills/commands.py:835
)
try:
response = input().strip().lower()
except (EOFError, KeyboardInterrupt):
console.print("\n[dim]Cancelled.[/dim]")
return
if response not in {"y", "yes"}:
console.print("[dim]Cancelled.[/dim]")
return
# Re-validate immediately before deletion to narrow the TOCTOU window
# (the user may have paused at the confirmation prompt).
if skill_dir.is_symlink():
console.print(
"[bold red]Error:[/bold red] Skill directory is a symlink. "
"Refusing to delete for safety."
)
raise SystemExit(1)
is_valid_path, path_error = _validate_skill_path(skill_dir, base_dir)
if not is_valid_path:
console.print(f"[bold red]Error:[/bold red] {path_error}")
raise SystemExit(1)
# Delete the skill directory
try:
shutil.rmtree(skill_dir)
except OSError as e:
console.print(
f"[bold red]Error:[/bold red] Failed to fully delete skill: {e}\n"
f"[yellow]Warning:[/yellow] Some files may have been partially removed.\n"
f"Please inspect: {skill_dir}/"
)
raise SystemExit(1) from e
if output_format == "json":View on GitHub (pinned to a1af029e6e)
Solutions
- Remove the symlink manually (`rm` the link, not `rm -r`) and manage the real directory in its source location
- Copy the real files into the skills directory (`cp -rL`) so it is no longer a symlink, then delete via the CLI
- Check `ls -la` / `readlink` to confirm which path is the link before removing
Example fix
// before dcode skills delete my-skill # ~/.claude/skills/my-skill is a stow symlink // after unlink ~/.claude/skills/my-skill # remove link; edit real files in ~/dotfiles
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def is_symlinked_skill(skills_root: Path, name: str) -> bool:
return (skills_root / name).is_symlink()
if is_symlinked_skill(Path(".claude/skills"), "my-skill"):
raise SystemExit("remove the symlink manually; CLI refuses to delete links") Type guard
def deletable_skill_dir(root: Path, name: str) -> bool:
p = root / name
return p.is_dir() and not p.is_symlink() Try / catch
proc = subprocess.run(["dcode", "skills", "delete", name, "--force"], capture_output=True, text=True)
if proc.returncode != 0 and "symlink" in proc.stderr:
os.unlink(skill_link) # remove the link itself, manage real files elsewhere Prevention
- Exclude skills dirs from stow/chezmoi symlink targets
- Use `cp -rL` to materialize real files before CLI deletes
- Check `is_symlink()` on skill paths in automation
When it happens
Trigger: `_delete` where `skill_dir.is_symlink()` is True at deletion time — typically dotfile-manager-managed skills (GNU stow, chezmoi), or manually symlinked folders into a shared repo.
Common situations: Users who manage skills via symlinked dotfiles try to delete through the CLI; the fix is to remove the symlink itself or manage the real files in their source location.
Related errors
- Error: Cannot determine base skills directory. Refusing to d
- dcode: {message}
- debug log directory is not a real directory: {path}
- Could not resolve {label} path {raw_path!r}: {exc}. Ensure t
- Error: Source agent '{source_agent}' not found or has no AGE
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/428d2e67efaf8cf9.
Report an issue: GitHub.