github/spec-kit · error · ValueError

Refusing to record symlinked manifest path: {rel} (symlinked

Error message

Refusing to record symlinked manifest path: {rel} (symlinked at {_walk.relative_to(self.project_root).as_posix()})

What it means

Raised when walking the components of a rel_path passed to record_file()/record_existing(): some ancestor directory of the file is a symlink. Recording it would let resolve() silently follow the link, so the file could be tracked (and later uninstalled) outside the project root.

Source

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

            _validate_rel_path(rel, self.project_root)
            # _validate_rel_path raised for any actually-escaping path. If we reach
            # here the path normalizes inside root (e.g. ``dir/../file.txt``).
            # Reject anyway: manifest keys must be canonical so ``check_modified``
            # and ``uninstall`` cannot key the same file under two paths.
            raise ValueError(
                f"Manifest paths must be canonical; '..' segments are not "
                f"allowed (got {rel})"
            )
        # Walk each path component before resolution so a symlinked ancestor
        # (e.g. ``linked_dir/file.txt`` where ``linked_dir`` is a symlink)
        # cannot be silently followed by ``_validate_rel_path().resolve()``
        # down to a target outside the project root. ``_ensure_safe_manifest_directory``
        # uses the same pattern.
        _walk = self.project_root
        for part in rel.parts:
            _walk = _walk / part
            if _walk.is_symlink():
                raise ValueError(
                    f"Refusing to record symlinked manifest path: {rel} "
                    f"(symlinked at {_walk.relative_to(self.project_root).as_posix()})"
                )
        abs_path = _validate_rel_path(rel, self.project_root)
        if not abs_path.is_file():
            raise ValueError(
                f"Manifest path is not a regular file: {rel}"
            )
        normalized = abs_path.relative_to(self.project_root).as_posix()
        self._files[normalized] = _sha256(abs_path)
        if recovered:
            self._recovered_files.add(normalized)
        else:
            # ``recovered=False`` means the caller is asserting this path is
            # managed-baseline now, not merely observed; drop any stale
            # recovered marker so future is_recovered() queries reflect the
            # transition. ``discard`` is a no-op when the key is absent.
            self._recovered_files.discard(normalized)

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Replace the symlinked ancestor with a real directory containing the file
  2. If sharing is needed, copy files into the project before recording them
  3. Skip recording linked paths; manage those files outside the manifest

Example fix

# before
.claude/commands -> ../../shared/commands
manifest.record_file(".claude/commands/build.md")
# after
cp -r ../../shared/commands/. .claude/commands/
manifest.record_file(".claude/commands/build.md")
Defensive patterns

Strategy: validation

Validate before calling

def has_symlinked_ancestor(root: Path, rel: Path) -> bool:
    cur = root
    for part in rel.parts:
        cur = cur / part
        if cur.is_symlink():
            return True
    return False

if not has_symlinked_ancestor(project_root, rel):
    manifest.record_file(rel)

Try / catch

try:
    manifest.record_file(rel)
except ValueError as exc:
    if "symlinked manifest path" in str(exc):
        copy_file_into_project(rel)  # then record the copied path
    else:
        raise

Prevention

When it happens

Trigger: record_file('linked_dir/cmd.md') where linked_dir is a symlink inside the project pointing elsewhere (inside or outside); the check is unconditional on the ancestor being a link, regardless of target.

Common situations: Monorepos symlinking shared command directories into per-package agent dirs; dotfile-managed .claude/.kilo directories; worktrees sharing command folders via links.

Related errors


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