PrefectHQ/fastmcp · error · FileExistsError
Skill directory already exists: {skill_dir}. Use overwrite=T
Error message
Skill directory already exists: {skill_dir}. Use overwrite=True to replace. What it means
`download_skill` refuses to overwrite an existing skill directory: if `target_dir/skill_name` already exists and `overwrite` is not True, this FileExistsError is raised. This protects previously downloaded (possibly locally modified) skills from being silently replaced.
Source
Thrown at fastmcp_slim/fastmcp/utilities/skills.py:175
async with Client("http://skills-server/mcp") as client:
skill_path = await download_skill(
client,
"pdf-processing",
"~/.claude/skills"
)
print(f"Downloaded to: {skill_path}")
```
"""
target_dir = Path(target_dir).expanduser().resolve()
skill_dir = (target_dir / skill_name).resolve()
# Security: ensure skill_dir stays within target_dir
if not skill_dir.is_relative_to(target_dir):
raise ValueError(f"Skill name {skill_name!r} would escape the target directory")
# Check if directory exists
if skill_dir.exists() and not overwrite:
raise FileExistsError(
f"Skill directory already exists: {skill_dir}. "
"Use overwrite=True to replace."
)
# Get manifest to know what files to download
manifest = await get_skill_manifest(client, skill_name)
# Create skill directory
skill_dir.mkdir(parents=True, exist_ok=True)
# Download each file
for file_info in manifest.files:
# Security: reject absolute paths and paths that escape skill_dir
if Path(file_info.path).is_absolute():
continue
file_path = (skill_dir / file_info.path).resolve()
if not file_path.is_relative_to(skill_dir):
continueView on GitHub (pinned to 1f02114297)
Solutions
- Pass `overwrite=True` if you intend to replace the existing skill directory.
- Choose a different `target_dir` (or delete/move the existing `target_dir/skill_name`) if you want to keep the old copy.
- For idempotent sync scripts, make existence of the directory the expected case and set `overwrite=True` deliberately.
Example fix
// before await download_skill(client, "code-review", "~/skills") # raises if exists // after await download_skill(client, "code-review", "~/skills", overwrite=True)
Defensive patterns
Strategy: try-catch
Validate before calling
from pathlib import Path
def skill_dir_conflicts(skill_name: str, target_dir: str | Path) -> bool:
skill_dir = (Path(target_dir).expanduser().resolve() / skill_name).resolve()
return skill_dir.exists() Try / catch
try:
await download_skill(client, skill_name, target_dir)
except FileExistsError:
await download_skill(client, skill_name, target_dir, overwrite=True) # or skip Prevention
- Decide up front whether your sync should overwrite, and set overwrite=True explicitly.
- Use a dedicated target directory per source so stale directories don't accumulate.
- Check for the existing directory before download when you need 'keep local edits' semantics.
When it happens
Trigger: Calling `download_skill(client, name, dir)` a second time into the same target directory without `overwrite=True`, or a `sync_skills` run where the skill directory already exists locally from a prior download or manual creation.
Common situations: Re-running a sync/download script without `overwrite`, partially completed previous downloads leaving the directory behind, or a directory coincidentally named like the skill already present in the target folder.
Related errors
- File not found: {self.file_path}
- Could not restrict access to CLI state
- Could not create the CLI state directory
- The CLI state lock must not be a symbolic link
- Could not lock CLI state
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/ac8305511127aee2.
Report an issue: GitHub.