affaan-m/ECC · error · ContractError

output bundle contains a special file

Error message

output bundle contains a special file: {entry.path}

What it means

During the bundle walk, any entry that is neither a symlink, directory, nor regular file is rejected as a 'special file' (FIFOs, sockets, device nodes). The contract requires the bundle to contain only plain directories and files so packaging/hashing is deterministic.

Solutions

  1. Delete the named special file at the reported path and rerun validation
  2. Find the process/tool that created the pipe/socket in the output dir and fix its cleanup
  3. Clean the output directory fully (rm -rf and regenerate) before validating
  4. Add a pre-validation sweep: find <bundle> ! -type f ! -type d -delete

Example fix

# before
mkfifo dist/bundle/pipe && validate_bundle(Path('dist/bundle'))
# after
rm -f dist/bundle/pipe && validate_bundle(Path('dist/bundle'))
Defensive patterns

Strategy: validation

Validate before calling

import os
special = [os.path.join(d, n) for d, ds, fs in os.walk(bundle_root)
           for n in ds + fs
           if not os.path.isfile(os.path.join(d, n)) and not os.path.isdir(os.path.join(d, n)) and not os.path.islink(os.path.join(d, n))]
assert not special, f'special files in bundle: {special}'

Try / catch

try:
    validate_bundle(root)
except ContractError as e:
    if 'special file' in str(e):
        print(f'Remove the special file and fix its creator: {e}')
    else:
        raise

Prevention

When it happens

Trigger: A FIFO, Unix socket, or device node exists inside the output bundle when validate_bundle walks it — typically left behind by a build/test process that created a pipe or socket in the output dir and didn't clean up.

Common situations: Dev servers leaving .sock files in a dist directory, mkfifo used in test fixtures, or a builder accidentally writing through a device path.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:114

    try:
        metadata = root.lstat()
    except FileNotFoundError:
        raise ContractError("output bundle is missing") from None
    if stat.S_ISLNK(metadata.st_mode):
        raise ContractError("output bundle root must not be a symlink")
    if not stat.S_ISDIR(metadata.st_mode):
        raise ContractError("output bundle root must be a directory")
    pending = [root]
    while pending:
        directory = pending.pop()
        with os.scandir(directory) as entries:
            for entry in entries:
                if entry.is_symlink():
                    raise ContractError(f"output bundle contains a symlink: {entry.path}")
                if entry.is_dir(follow_symlinks=False):
                    pending.append(Path(entry.path))
                elif not entry.is_file(follow_symlinks=False):
                    raise ContractError(f"output bundle contains a special file: {entry.path}")


def validate_genre_specs(specs: list[dict[str, Any]]) -> None:
    """Require complete, semantically distinct numbered genre specs."""
    if not specs:
        raise ContractError("at least one genre spec is required")
    numbers = [spec.get("number") for spec in specs]
    if len(numbers) != len(set(numbers)):
        raise ContractError("genre numbers must be distinct")

    fingerprints = [spec.get("style_fingerprint") for spec in specs]
    signatures = [_semantic_signature(spec) for spec in specs]
    if len(fingerprints) != len(set(fingerprints)) or len(signatures) != len(set(signatures)):
        raise ContractError("genre references collapsed into a generic style; distinct specs required")

    for spec in specs:
        if spec.get("dry_run") is not True:
            raise ContractError(f"genre {spec.get('number')} crosses the dry-run boundary")

View on GitHub (pinned to 8321021c54)