affaan-m/ECC · error · ContractError
receipt source cannot be securely read
Error message
receipt source cannot be securely read: {source_path} What it means
If reading a receipt source for hashing fails with a generic OSError (permission denied, I/O error, too many open files, etc.), the validator converts it to ContractError('receipt source cannot be securely read: <path>'). The word 'securely' signals the validator refuses to proceed without a trustworthy digest; it never skips unreadable sources regardless of policy.
Solutions
- Fix filesystem permissions so the validating user can read the source (chmod/chown or run as the owning user)
- Check the underlying cause (dmesg/disk health for EIO; ulimit -n for EMFILE) and remediate
- Copy sources to a local readable location and update the receipt paths + digests
- Re-run validation after access is restored — this error is never bypassed by allow_unavailable
Example fix
# before -rw------- 1 other dev src/data.csv # after chmod o+r src/data.csv # or run validation as the file owner
Defensive patterns
Strategy: try-catch
Validate before calling
import os
def readable_sources(receipt):
unreadable = [s['path'] for s in sources
if not os.access(s['path'], os.R_OK)]
return unreadable # must be empty Type guard
def is_readable_file(path: str) -> bool:
p = Path(path)
return p.is_file() and os.access(p, os.R_OK) Try / catch
try:
validate_artifact_receipt(receipt, out_dir)
except ContractError as e:
if 'cannot be securely read' in str(e):
path = str(e).rsplit(': ', 1)[1]
# fix permissions / ulimit / disk error, then retry; never bypass
raise Prevention
- Ensure the validating user has read permission on every source (chmod/chown)
- Raise ulimit -n when validating receipts with many sources
- Run CI as the same user that owns checked-in restricted files
- Monitor disk health; EIO on the source volume surfaces here
When it happens
Trigger: Source file lacks read permission for the validating user; disk I/O error; process hit the open-file limit; encrypted/locked file; source on a flaky network mount returning EIO during validation.
Common situations: Files checked in with 0600 owned by another user (CI runs as different uid); umask/ACL changes after generation; headless CI lacking access to a mounted secret-backed source; ulimit -n too low with many sources.
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
- Cannot read local asset
- ECC_MEMORY_INCOMPLETE
- Failed to create directory
- destination is not writable by the current user: . Fix the…
- Receipt output already exists
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/0ae48bc5efedcd13.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:362
raise ContractError("receipt must declare an explicit source availability policy")
for source_path, expected_digest in sorted(known_sources):
path = Path(source_path)
try:
metadata = path.lstat()
except FileNotFoundError:
if source_policy == "require_available":
raise ContractError(f"receipt source is unavailable: {source_path}") from None
continue
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
raise ContractError(f"receipt source is not a safe regular file: {source_path}")
try:
actual_digest = _sha256(path)
except FileNotFoundError:
if source_policy == "require_available":
raise ContractError(f"receipt source is unavailable: {source_path}") from None
continue
except OSError:
raise ContractError(f"receipt source cannot be securely read: {source_path}") from None
if actual_digest != expected_digest:
raise ContractError(f"receipt source SHA-256 changed after generation: {source_path}")
emitted = {
path.relative_to(out_dir).as_posix()
for path in out_dir.rglob("*")
if path.is_file() and path.name != "receipt.json"
}
bound_paths: list[str] = []
for entry in entries:
relative = entry.get("path")
if not isinstance(relative, str) or not relative:
raise ContractError("artifact path must be a non-empty relative path")
bound_paths.append(relative)
if len(bound_paths) != len(set(bound_paths)):
raise ContractError("receipt contains duplicate artifact paths")
missing = emitted - set(bound_paths)
extra = set(bound_paths) - emittedView on GitHub (pinned to 8321021c54)