github/spec-kit · error · ValueError

Refusing to overwrite symlinked integration manifest path: {

Error message

Refusing to overwrite symlinked integration manifest path: {label}

What it means

Raised by _ensure_safe_manifest_destination when the manifest file path itself (.specify/integrations/<key>.manifest.json) is a symlink. Overwriting a symlinked file would follow the link and could clobber a file outside the project, so the write is refused up front.

Source

Thrown at src/specify_cli/integrations/manifest.py:94

            try:
                current.resolve().relative_to(root_resolved)
            except (OSError, ValueError):
                raise ValueError(f"Integration manifest directory escapes project root: {label}") from None
            continue
        current.mkdir()
        try:
            current.resolve().relative_to(root_resolved)
        except (OSError, ValueError):
            raise ValueError(f"Integration manifest directory escapes project root: {label}") from None


def _ensure_safe_manifest_destination(root: Path, path: Path) -> None:
    """Refuse manifest writes that would escape the project or follow symlinks."""
    root_resolved = root.resolve()
    _ensure_safe_manifest_directory(root, path.parent)
    label = _manifest_path_label(root, path)
    if path.is_symlink():
        raise ValueError(f"Refusing to overwrite symlinked integration manifest path: {label}")
    if path.exists():
        if not path.is_file():
            raise ValueError(f"Integration manifest path is not a file: {label}")
        try:
            path.resolve().relative_to(root_resolved)
        except (OSError, ValueError):
            raise ValueError(f"Integration manifest path escapes project root: {label}") from None


class IntegrationManifest:
    """Tracks files installed by a single integration.

    Parameters:
        key:          Integration identifier (e.g. ``"copilot"``).
        project_root: Absolute path to the project directory.
        version:      CLI version string recorded in the manifest.
        resolve_project_root: Resolve ``project_root`` before using it.
    """

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Delete the symlink; save() will write a regular file (rm .specify/integrations/<key>.manifest.json)
  2. If you need the file elsewhere, copy instead of symlink after the CLI writes it
  3. Configure the dotfile manager to ignore .specify/integrations

Example fix

# before
ln -s ~/shared/claude.manifest.json .specify/integrations/claude.manifest.json
# after
rm .specify/integrations/claude.manifest.json  # save() recreates a regular file
Defensive patterns

Strategy: validation

Validate before calling

mp = manifest.manifest_path
if mp.is_symlink():
    mp.unlink()  # let save() write a regular file

Try / catch

try:
    manifest.save()
except ValueError as exc:
    if "symlinked integration manifest path" in str(exc):
        manifest.manifest_path.unlink()
        manifest.save()
    else:
        raise

Prevention

When it happens

Trigger: IntegrationManifest.save() (or any write to manifest_path) when <key>.manifest.json is a symlink — commonly pointing at a shared manifest in a dotfiles repo or another worktree.

Common situations: Developers sharing manifests across worktrees via symlinks; dotfile managers (stow, chezmoi) that link config files; CI restoring cached manifests as links.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/754a402ec0e238b5. Report an issue: GitHub.