affaan-m/ECC · error · ValueError

Cannot create exclusive receipt

Error message

Cannot create exclusive receipt: {output}

What it means

ingest_assets writes the receipt with exclusive creation (open mode 'x'); any OSError during creation or writing is re-raised as this ValueError. Because the earlier exists() gate passed, this usually means a race (file created between check and open) or a filesystem-level failure (permission, directory missing, disk full).

Solutions

  1. Ensure the output directory exists and is writable (mkdir -p; check permissions).
  2. Serialize runs or use unique output names per worker to avoid the check-then-create race.
  3. Retry after resolving the underlying OSError; the chained exception message contains the cause.
  4. Check disk space/quota if the message persists.

Example fix

# before
receipt = ingest_assets('config.json', 'out/receipt.json')  # out/ missing
# after
from pathlib import Path
Path('out').mkdir(parents=True, exist_ok=True)
receipt = ingest_assets('config.json', 'out/receipt.json')
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path
out = Path('out/receipt.json')
out.parent.mkdir(parents=True, exist_ok=True)
assert not out.exists() and os.access(out.parent, os.W_OK)

Try / catch

try:
    ingest_assets(cfg, out_path)
except ValueError as e:
    if 'Cannot create exclusive receipt' in str(e):
        check_permissions_and_disk(out_path); use_unique_path_and_retry()
    else:
        raise

Prevention

When it happens

Trigger: Two processes calling ingest_assets with the same output path concurrently; output directory lacking write permission; parent directory does not exist; disk quota/full filesystem; output path resolving to a special file; Windows sharing violations.

Common situations: CI parallel jobs writing the same receipt path; running ingest in a read-only mounted directory; missing output directory after a clean step; NFS/SMB stale-lock style collisions.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

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

        if provenance is not None:
            record['provider_provenance'] = provenance
        records.append(record)
    inputs = config.get('input_artifacts', [])
    if not isinstance(inputs, list):
        raise ValueError('input_artifacts must be a list of local paths')
    receipt = dict(schema=SCHEMA, provider_calls=0, provider_execution=False,
                   assets=records, input_artifacts=[_fingerprint(_path(p, base)) for p in inputs])
    if binding is not None:
        receipt['bundle_receipt'] = binding
    if 'genre_spec' in config:
        receipt['genre_spec'] = _fingerprint(_path(config['genre_spec'], base))
    # All inputs already exist, so the output-exists gate also prevents collisions.
    try:
        with output.open('x', encoding='utf-8') as stream:
            json.dump(receipt, stream, indent=2, sort_keys=True)
            stream.write('\n')
    except OSError as exc:
        raise ValueError(f'Cannot create exclusive receipt: {output}') from exc
    return receipt


def validate_assets(receipt_path: str | Path) -> dict[str, Any]:
    """Re-hash every bound file and validate receipt semantics; no remote calls."""
    path = _path(receipt_path, Path.cwd())
    receipt = _load(path)
    if receipt.get('schema') != SCHEMA:
        raise ValueError('Unsupported asset receipt schema')
    if type(receipt.get('provider_calls')) is not int or receipt['provider_calls'] != 0:
        raise ValueError('Local ingestion must have zero provider calls')
    if receipt.get('provider_execution') is not False:
        raise ValueError('Local ingestion cannot claim provider execution')
    _, requests = (_bundle(receipt['bundle_receipt'], path.parent, True)
                   if 'bundle_receipt' in receipt else (None, {}))
    for asset in _assets(receipt.get('assets')):
        _taste(asset, requests, True)
        _verify_binding(asset, path.parent, asset['modality'])

View on GitHub (pinned to 8321021c54)