affaan-m/ECC · error · ContractError
artifact path must be a non-empty relative path
Error message
artifact path must be a non-empty relative path
What it means
Each artifact entry in receipt.json must carry a 'path' that is a non-empty string. This ContractError is raised when an entry's path is missing, null, an empty string, or not a string at all. The library requires a well-formed relative path to bind the receipt entry to an emitted file under the output directory.
Solutions
- Fix the receipt entry so every artifact has a non-empty string 'path' relative to the output directory.
- Regenerate the receipt with tasteforge instead of editing it by hand.
- Validate receipt.json structure (e.g. jq '.artifacts[] | select(.path == null or .path == "")') before running the validator.
Example fix
// before
{"artifacts": [{"bytes": 120, "sha256": "abc..."}]}
// after
{"artifacts": [{"path": "images/hero.png", "bytes": 120, "sha256": "abc..."}]} Defensive patterns
Strategy: validation
Validate before calling
for i, entry in enumerate(receipt.get('artifacts', [])):
p = entry.get('path')
if not isinstance(p, str) or not p:
raise SystemExit(f'artifact[{i}].path must be a non-empty string') Type guard
def has_valid_path(entry: dict) -> bool:
return isinstance(entry.get('path'), str) and len(entry['path']) > 0 Try / catch
try:
validate_artifact_receipt(out_dir, entries)
except ContractError as e:
if 'non-empty relative path' in str(e):
fix_receipt_paths(receipt_path) # repair or regenerate
else:
raise Prevention
- Never hand-edit receipt.json; regenerate it with the tool.
- Schema-check receipts programmatically before validation.
- Keep a JSON schema for the receipt format and lint against it in CI.
When it happens
Trigger: Calling validate_artifact_receipt()/validate_bundle() with a hand-edited or programmatically generated receipt.json whose artifacts[] entry lacks 'path', has path: "", or path set to a non-string value (number, null, object).
Common situations: Manually pruning or editing receipt.json entries and accidentally deleting or emptying the path field; a custom generation script writing malformed entries; JSON schema drift between tool versions.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- receipt contains duplicate artifact paths
- receipt contains an invalid source
- receipt must be a list
- Unsupported asset receipt schema
- -32602
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a908d14f251aea9a.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:375
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) - emitted
if missing:
raise ContractError(f"unbound emitted artifact: {sorted(missing)}")
if extra:
raise ContractError(f"receipt binds missing artifact: {sorted(extra)}")
for entry in entries:
relative = entry.get("path")
assert isinstance(relative, str)
path = (out_dir / relative).resolve()
try:
path.relative_to(out_dir)
except ValueError as error:
raise ContractError(f"artifact path escapes output directory: {relative}") from errorView on GitHub (pinned to 8321021c54)