affaan-m/ECC · error · ValueError

Cannot read local asset

Error message

Cannot read local asset: {path}

What it means

_fingerprint wraps the entire hash operation (os.open, fdopen read loop, final stat) in try/except OSError and re-raises as ValueError('Cannot read local asset: {path}'). This means an OS-level failure occurred while opening or reading the file: permission denied, file missing at open time, or I/O error mid-read.

Solutions

  1. Check the file exists and is readable: os.access(path, os.R_OK) after os.path.isfile(path).
  2. Fix filesystem permissions (chmod/chown) so the running user can read the asset.
  3. Re-run ingestion if the file was being written concurrently; ensure assets are finalized before ingest.
  4. Check mount/disk health if errors are EIO (dmesg, mount status).

Example fix

# before
path = manifest['path']  # may not exist yet

# after
p = Path(manifest['path'])
if not (p.is_file() and os.access(p, os.R_OK)):
    raise FileNotFoundError(f'Asset missing/unreadable before ingest: {p}')
Defensive patterns

Strategy: try-catch

Validate before calling

import os
ok = os.path.isfile(path) and os.access(path, os.R_OK)

Try / catch

try:
    bundle = _bundle(manifest_path)
except ValueError as e:
    if str(e).startswith('Cannot read local asset'):
        path = str(e).rsplit(': ', 1)[1]
        check_permissions_and_existence(path)

Prevention

When it happens

Trigger: os.open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK) raises OSError (ENOENT, EACCES, ELOOP), or stream.read()/path.stat() raises OSError during hashing via any of the callers (_load, _provenance, _verify_binding, _bundle, ingest_assets).

Common situations: Asset file permissions too restrictive (readable only by another user); file deleted between manifest listing and ingestion; NFS/stale mount returning EIO; running the pipeline as a different user in CI; path containing a typo.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

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

    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)
            for chunk in iter(lambda: stream.read(1024 * 1024), b''):
                digest.update(chunk)
            after = os.fstat(stream.fileno())
        final = path.stat()
    except OSError as exc:
        raise ValueError(f'Cannot read local asset: {path}') from exc
    def identity(info: os.stat_result) -> tuple[int, ...]:
        return (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns, info.st_ctime_ns)
    if len({identity(info) for info in (before, opened, after, final)}) != 1:
        raise ValueError(f'File changed while hashing: {path}')
    if modality == '3d_asset':
        if len(header) != 12:
            raise ValueError(f'Truncated GLB header: {path}')
        magic, version, size = struct.unpack('<4sII', header)
        if magic != b'glTF' or version != 2 or size != final.st_size:
            raise ValueError(f'Invalid GLB magic, version or declared size: {path}')
    return dict(path=str(path), bytes=final.st_size, sha256=digest.hexdigest())


def _load(path: Path) -> dict[str, Any]:
    _fingerprint(path)
    try:
        data = json.loads(path.read_text(encoding='utf-8'))
    except (ValueError, OSError) as exc:

View on GitHub (pinned to 8321021c54)