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):
            continue

View on GitHub (pinned to 1f02114297)

Solutions

  1. Pass `overwrite=True` if you intend to replace the existing skill directory.
  2. Choose a different `target_dir` (or delete/move the existing `target_dir/skill_name`) if you want to keep the old copy.
  3. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/ac8305511127aee2. Report an issue: GitHub.