affaan-m/ECC · error · ValueError
artifact SHA-256 mismatch
Error message
artifact SHA-256 mismatch
What it means
`_read_local` computes the SHA-256 of every byte it reads and, when the caller supplied `expected_hash`, rejects the artifact if the computed digest differs. This is the final integrity gate: it proves the file content is byte-identical to what was attested when the application request was created. A mismatch means the content changed even if size and timestamps happened to line up.
Solutions
- Recompute the artifact's SHA-256 (`hashlib.sha256(bytes).hexdigest()`) and regenerate the application request with the fresh digest, then retry.
- Verify with `shasum -a 256 <file>` which side is stale — the file or the recorded hash — and regenerate whichever is out of date.
- Re-download/rebuild the artifact from the trusted source so its content matches the pinned hash.
- Ensure the hash producer normalizes output (64 lowercase hex, no `sha256:` prefix) identically to what the pipeline records.
Example fix
// before
# request pinned hash of v1 artifact; v2 was rebuilt without refreshing request
// after
fresh = hashlib.sha256(Path("/out/artifact.json").read_bytes()).hexdigest()
write_request(artifact_path, sha256=fresh)
req = load_application_request("/out/request.json") Defensive patterns
Strategy: validation
Validate before calling
import hashlib, os
def sha256_file(path: str) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()
def assert_hash_matches(path: str, expected_hash: str) -> None:
actual = sha256_file(path)
if actual != expected_hash.lower().removeprefix("sha256:"):
raise ValueError(f"hash mismatch for {path}: expected {expected_hash}, got {actual}") Try / catch
try:
req = load_application_request(p)
except ValueError as e:
if str(e) == "artifact SHA-256 mismatch":
write_request(p, sha256=sha256_file(p), size=os.path.getsize(p)) # re-pin metadata
req = load_application_request(p)
else:
raise Prevention
- Regenerate the pinned hash every time the artifact is rebuilt — never carry hashes across rebuilds.
- Use binary-safe transfer (scp/rsync, binary FTP) so content is not altered in transit.
- Record hash and size in one pass over the same final file that ships.
- Never edit artifacts in place after hashing; publish a new versioned file instead.
When it happens
Trigger: Calling `load_application_request`/`_artifact` where the recorded `expected_hash` was computed over an older or different version of the file; the file was modified in a way that preserves size (in-place byte edits, timestamp-preserving writes); the hash was recorded from the wrong artifact or with different normalization (e.g. uppercase/prefixed hex handled upstream).
Common situations: Rebuilding the artifact without refreshing the request document; hot-patching a file in place; copying artifacts between machines with a lossy transfer; recording the hash of a pre-processed file but shipping the post-processed one.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- approved text hash does not match
- artifact SHA-256 does not match receipt
- candidate content address or canonical configuration is…
- harness health evidence integrity verification failed
- digest mismatch: expected , got .
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/4bf9cb36c18ed94d.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/integration.py:151
digest, chunks, count = hashlib.sha256(), [], 0
while data := os.read(descriptor, 65536):
count += len(data)
if count > expected_size:
raise ValueError("artifact byte count exceeded during reading")
digest.update(data)
if parse_json:
chunks.append(data)
# Rewalk the named path: a pinned old directory fd can outlive a rename.
fresh_parent = _parent_fd(path)
try:
after = os.stat(path.name, dir_fd=fresh_parent, follow_symlinks=False)
finally:
os.close(fresh_parent)
if (_identity(before) != _identity(os.fstat(descriptor))
or _identity(before) != _identity(after)):
raise ValueError("artifact changed during reading")
if expected_hash is not None and digest.hexdigest() != expected_hash:
raise ValueError("artifact SHA-256 mismatch")
return _load_json(b"".join(chunks)) if parse_json else None
except (OSError, AttributeError) as exc:
raise ValueError("local artifact unavailable or unsafe") from exc
finally:
if descriptor is not None:
os.close(descriptor)
if parent is not None:
os.close(parent)
def load_application_request(path: str | Path) -> dict:
"""Load only a bounded resident request; never follow a config symlink."""
value = _read_local(str(Path(path).absolute()), parse_json=True)
if not isinstance(value, dict):
raise ValueError("application request must be a JSON object")
return value
View on GitHub (pinned to 8321021c54)