affaan-m/ECC · error · ValueError

Symlinks are not accepted

Error message

Symlinks are not accepted: {part}

What it means

_path walks the path and all of its parents and refuses any component that is a symlink, before resolving '..'. This prevents symlink-based path traversal and ensures fingerprints/provenance bind to the real file location. Any symlinked component raises ValueError naming the offending path component.

Solutions

  1. Use path.resolve() (or os.path.realpath) to get the real path and pass that instead.
  2. Move or copy assets into a non-symlinked location and reference them directly.
  3. On macOS, use /private/tmp instead of /tmp when staging assets.
  4. Check which component is flagged in the message and replace that symlink with a real directory.

Example fix

// before
ingest_assets(sp, paths=["/tmp/renders/clip.mp4"])  # /tmp is a symlink

// after
from pathlib import Path
real = Path("/tmp/renders/clip.mp4").resolve()  # e.g. /private/tmp/renders/clip.mp4
ingest_assets(sp, paths=[str(real)])
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
def assert_no_symlinks(p: str):
    path = Path(p).expanduser()
    for part in (path, *path.parents):
        if part.is_symlink():
            raise ValueError(f"symlink component: {part}")

Type guard

def is_symlink_free(p: str) -> bool:
    from pathlib import Path
    path = Path(p).expanduser()
    return not any(part.is_symlink() for part in (path, *path.parents))

Try / catch

try:
    assets = ingest_assets(sp, paths=paths)
except ValueError as e:
    if "Symlinks are not accepted" in str(e):
        paths = [str(Path(p).resolve()) for p in paths]
        assets = ingest_assets(sp, paths=paths)
    else:
        raise

Prevention

When it happens

Trigger: Passing a path through a symlinked directory (e.g. /tmp being a symlink to /private/tmp on macOS, or ~/assets pointing elsewhere) to ingest_assets/validate_assets/_bundle/_provenance.

Common situations: macOS /tmp → /private/tmp; home directories symlinked into cloud-sync folders; dotfile-managed directories (stow) that are symlinks; project dirs reached via a convenience symlink.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/257199d5fb880bfb. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/assets.py:39


def _text(value: Any, name: str) -> str:
    if not isinstance(value, str) or not value.strip():
        raise ValueError(f'{name} must be a nonempty string')
    return value


def _path(value: Any, base: Path) -> Path:
    raw = _text(str(value) if isinstance(value, Path) else value, 'path')
    if '://' in raw or raw.startswith(('file:', 'http:', 'https:')):
        raise ValueError('Only local filesystem paths are supported')
    path = Path(raw).expanduser()
    if not path.is_absolute():
        path = base / path
    # Check before resolving '..' to avoid hiding a symlink in the path.
    for part in (path, *path.parents):
        if part.is_symlink():
            raise ValueError(f'Symlinks are not accepted: {part}')
    return path.resolve()


def _fingerprint(path: Path, modality: str | None = None) -> dict[str, Any]:
    _path(path, Path.cwd())
    try:
        before = path.stat()
        if not stat.S_ISREG(before.st_mode):
            raise ValueError(f'Not a regular file: {path}')
        flags = os.O_RDONLY | getattr(os, 'O_NOFOLLOW', 0) | os.O_NONBLOCK
        fd = os.open(path, flags)
        with os.fdopen(fd, 'rb') as stream:
            opened = os.fstat(stream.fileno())
            if not stat.S_ISREG(opened.st_mode):
                raise ValueError(f'Not a regular file: {path}')
            digest = hashlib.sha256()
            header = stream.read(12)
            digest.update(header)

View on GitHub (pinned to 8321021c54)