{"record":{"id":"4bf9cb36c18ed94d","repo":"affaan-m/ECC","slug":"artifact-sha-256-mismatch","errorCode":null,"errorMessage":"artifact SHA-256 mismatch","messagePattern":"artifact SHA-256 mismatch","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":151,"sourceCode":"        digest, chunks, count = hashlib.sha256(), [], 0\n        while data := os.read(descriptor, 65536):\n            count += len(data)\n            if count > expected_size:\n                raise ValueError(\"artifact byte count exceeded during reading\")\n            digest.update(data)\n            if parse_json:\n                chunks.append(data)\n        # Rewalk the named path: a pinned old directory fd can outlive a rename.\n        fresh_parent = _parent_fd(path)\n        try:\n            after = os.stat(path.name, dir_fd=fresh_parent, follow_symlinks=False)\n        finally:\n            os.close(fresh_parent)\n        if (_identity(before) != _identity(os.fstat(descriptor))\n                or _identity(before) != _identity(after)):\n            raise ValueError(\"artifact changed during reading\")\n        if expected_hash is not None and digest.hexdigest() != expected_hash:\n            raise ValueError(\"artifact SHA-256 mismatch\")\n        return _load_json(b\"\".join(chunks)) if parse_json else None\n    except (OSError, AttributeError) as exc:\n        raise ValueError(\"local artifact unavailable or unsafe\") from exc\n    finally:\n        if descriptor is not None:\n            os.close(descriptor)\n        if parent is not None:\n            os.close(parent)\n\n\ndef load_application_request(path: str | Path) -> dict:\n    \"\"\"Load only a bounded resident request; never follow a config symlink.\"\"\"\n    value = _read_local(str(Path(path).absolute()), parse_json=True)\n    if not isinstance(value, dict):\n        raise ValueError(\"application request must be a JSON object\")\n    return value\n\n","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L133-L169","documentation":"`_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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\n# request pinned hash of v1 artifact; v2 was rebuilt without refreshing request\n// after\nfresh = hashlib.sha256(Path(\"/out/artifact.json\").read_bytes()).hexdigest()\nwrite_request(artifact_path, sha256=fresh)\nreq = load_application_request(\"/out/request.json\")","handlingStrategy":"validation","validationCode":"import hashlib, os\ndef sha256_file(path: str) -> str:\n    h = hashlib.sha256()\n    with open(path, \"rb\") as f:\n        for chunk in iter(lambda: f.read(65536), b\"\"):\n            h.update(chunk)\n    return h.hexdigest()\ndef assert_hash_matches(path: str, expected_hash: str) -> None:\n    actual = sha256_file(path)\n    if actual != expected_hash.lower().removeprefix(\"sha256:\"):\n        raise ValueError(f\"hash mismatch for {path}: expected {expected_hash}, got {actual}\")","typeGuard":null,"tryCatchPattern":"try:\n    req = load_application_request(p)\nexcept ValueError as e:\n    if str(e) == \"artifact SHA-256 mismatch\":\n        write_request(p, sha256=sha256_file(p), size=os.path.getsize(p))  # re-pin metadata\n        req = load_application_request(p)\n    else:\n        raise","preventionTips":["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."],"tags":["integrity","sha256","checksum"],"backgroundTag":"checksum-mismatch","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}