{"record":{"id":"428d2e67efaf8cf9","repo":"langchain-ai/deepagents","slug":"error-skill-directory-is-a-symlink-refusing-to-d","errorCode":null,"errorMessage":"Error: Skill directory is a symlink. Refusing to delete for safety.","messagePattern":"Error: Skill directory is a symlink\\. Refusing to delete for safety\\.","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/skills/commands.py","lineNumber":835,"sourceCode":"        )\n        try:\n            response = input().strip().lower()\n        except (EOFError, KeyboardInterrupt):\n            console.print(\"\\n[dim]Cancelled.[/dim]\")\n            return\n\n        if response not in {\"y\", \"yes\"}:\n            console.print(\"[dim]Cancelled.[/dim]\")\n            return\n\n    # Re-validate immediately before deletion to narrow the TOCTOU window\n    # (the user may have paused at the confirmation prompt).\n    if skill_dir.is_symlink():\n        console.print(\n            \"[bold red]Error:[/bold red] Skill directory is a symlink. \"\n            \"Refusing to delete for safety.\"\n        )\n        raise SystemExit(1)\n\n    is_valid_path, path_error = _validate_skill_path(skill_dir, base_dir)\n    if not is_valid_path:\n        console.print(f\"[bold red]Error:[/bold red] {path_error}\")\n        raise SystemExit(1)\n\n    # Delete the skill directory\n    try:\n        shutil.rmtree(skill_dir)\n    except OSError as e:\n        console.print(\n            f\"[bold red]Error:[/bold red] Failed to fully delete skill: {e}\\n\"\n            f\"[yellow]Warning:[/yellow] Some files may have been partially removed.\\n\"\n            f\"Please inspect: {skill_dir}/\"\n        )\n        raise SystemExit(1) from e\n\n    if output_format == \"json\":","sourceCodeStart":817,"sourceCodeEnd":853,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/skills/commands.py#L817-L853","documentation":"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.","triggerScenarios":"`_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.","commonSituations":"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.","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"],"exampleFix":"// before\ndcode skills delete my-skill   # ~/.claude/skills/my-skill is a stow symlink\n// after\nunlink ~/.claude/skills/my-skill   # remove link; edit real files in ~/dotfiles","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_symlinked_skill(skills_root: Path, name: str) -> bool:\n    return (skills_root / name).is_symlink()\n\nif is_symlinked_skill(Path(\".claude/skills\"), \"my-skill\"):\n    raise SystemExit(\"remove the symlink manually; CLI refuses to delete links\")","typeGuard":"def deletable_skill_dir(root: Path, name: str) -> bool:\n    p = root / name\n    return p.is_dir() and not p.is_symlink()","tryCatchPattern":"proc = subprocess.run([\"dcode\", \"skills\", \"delete\", name, \"--force\"], capture_output=True, text=True)\nif proc.returncode != 0 and \"symlink\" in proc.stderr:\n    os.unlink(skill_link)  # remove the link itself, manage real files elsewhere","preventionTips":["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"],"tags":["cli","symlink","safety"],"backgroundTag":"symlink-deletion-refused","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}