{"record":{"id":"4304f864d9d48152","repo":"affaan-m/ECC","slug":"not-a-regular-file-path","errorCode":null,"errorMessage":"Not a regular file: {path}","messagePattern":"Not a regular file: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":48,"sourceCode":"    raw = _text(str(value) if isinstance(value, Path) else value, 'path')\n    if '://' in raw or raw.startswith(('file:', 'http:', 'https:')):\n        raise ValueError('Only local filesystem paths are supported')\n    path = Path(raw).expanduser()\n    if not path.is_absolute():\n        path = base / path\n    # Check before resolving '..' to avoid hiding a symlink in the path.\n    for part in (path, *path.parents):\n        if part.is_symlink():\n            raise ValueError(f'Symlinks are not accepted: {part}')\n    return path.resolve()\n\n\ndef _fingerprint(path: Path, modality: str | None = None) -> dict[str, Any]:\n    _path(path, Path.cwd())\n    try:\n        before = path.stat()\n        if not stat.S_ISREG(before.st_mode):\n            raise ValueError(f'Not a regular file: {path}')\n        flags = os.O_RDONLY | getattr(os, 'O_NOFOLLOW', 0) | os.O_NONBLOCK\n        fd = os.open(path, flags)\n        with os.fdopen(fd, 'rb') as stream:\n            opened = os.fstat(stream.fileno())\n            if not stat.S_ISREG(opened.st_mode):\n                raise ValueError(f'Not a regular file: {path}')\n            digest = hashlib.sha256()\n            header = stream.read(12)\n            digest.update(header)\n            for chunk in iter(lambda: stream.read(1024 * 1024), b''):\n                digest.update(chunk)\n            after = os.fstat(stream.fileno())\n        final = path.stat()\n    except OSError as exc:\n        raise ValueError(f'Cannot read local asset: {path}') from exc\n    def identity(info: os.stat_result) -> tuple[int, ...]:\n        return (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns, info.st_ctime_ns)\n    if len({identity(info) for info in (before, opened, after, final)}) != 1:","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L30-L66","documentation":"_fingerprint verifies via stat (and again via fstat after opening with O_NOFOLLOW) that the target is a regular file before hashing it. Directories, FIFOs, sockets, device nodes, and files swapped to non-regular between checks raise ValueError. This guarantees the fingerprint describes actual file content.","triggerScenarios":"Calling _fingerprint (directly or via ingest_assets/validate_assets/_provenance/_bundle/_load) with a directory path, a named pipe, or a path that changes type between stat and open.","commonSituations":"Passing a directory instead of a file in an asset list; pointing at a Unix socket or log pipe; a renderer writing output as a FIFO; TOCTOU swap where the file is replaced mid-operation.","solutions":["Check the path with Path(p).is_file() and pass regular files only.","If you passed a directory, list it and fingerprint each file individually.","Stop whatever process is exposing the path as a pipe/socket and produce a regular file.","Re-run ingestion if the file was swapped concurrently; use stable output directories."],"exampleFix":"// before\ningest_assets(sp, paths=[\"/renders\"])  # directory\n\n// after\nfrom pathlib import Path\nfiles = [str(p) for p in Path(\"/renders\").iterdir() if p.is_file()]\ningest_assets(sp, paths=files)","handlingStrategy":"validation","validationCode":"import stat\ndef is_regular_file(p) -> bool:\n    try:\n        return stat.S_ISREG(Path(p).stat().st_mode)\n    except OSError:\n        return False","typeGuard":"def is_fingerprintable(p) -> bool:\n    from pathlib import Path\n    path = Path(p)\n    return path.exists() and path.is_file() and not path.is_symlink()","tryCatchPattern":"try:\n    assets = ingest_assets(sp, paths=paths)\nexcept ValueError as e:\n    if \"Not a regular file\" in str(e):\n        paths = [p for p in paths if Path(p).is_file()]\n        assets = ingest_assets(sp, paths=paths)\n    else:\n        raise","preventionTips":["Expand directories into individual files before ingestion.","Never point asset lists at sockets, FIFOs, or device nodes.","Write renderer output atomically to regular files before fingerprinting."],"tags":["file-type","regular-file-required","input-validation"],"backgroundTag":"incompatible-source-type","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}