{"record":{"id":"d1283fd4b4a3c28e","repo":"jdx/mise","slug":"remote-cache-action-bytes-do-not-match-cache-key","errorCode":null,"errorMessage":"remote cache action bytes do not match cache key","messagePattern":"remote cache action bytes do not match cache key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":449,"sourceCode":"        let nonce = crate::rand::random_string(8);\n        Ok(TaskCacheStoreWrite {\n            artifact_path: self.staging_dir.join(format!(\"{key}.part-{nonce}.tar.zst\")),\n            manifest_path: self.staging_dir.join(format!(\"{key}.part-{nonce}.json\")),\n        })\n    }\n\n    async fn commit(\n        &self,\n        key: &str,\n        action: &[u8],\n        write: &TaskCacheStoreWrite,\n        manifest: &[u8],\n        has_artifact: bool,\n    ) -> Result<()> {\n        validate_remote_key(key)?;\n        let action_digest = CacheDigest::blake3(action);\n        if action_digest.hash != key {\n            bail!(\"remote cache action bytes do not match cache key\");\n        }\n        let manifest: CacheManifest = serde_json::from_slice(manifest)?;\n        if manifest.key != key {\n            bail!(\"local task cache manifest does not match remote action\");\n        }\n        let metadata = canonical_json(&serde_json::to_value(\n            RemoteClientMetadata::from_manifest(&manifest),\n        )?)?;\n        let mut uploads = vec![\n            BlobUpload {\n                digest: action_digest.clone(),\n                source: BlobSource::Bytes(action.to_vec()),\n            },\n            BlobUpload {\n                digest: CacheDigest::blake3(&metadata),\n                source: BlobSource::Bytes(metadata),\n            },\n        ];","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/task/task_cache_store.rs#L431-L467","documentation":"During commit, the blake3 digest of the supplied action bytes did not equal the cache key they are being stored under. Cache keys are derived from action content, so a mismatch means the key and payload are inconsistent and the entry would be unretrievable (or would alias another action); the library rejects the write.","triggerScenarios":"Calling commit() with a key computed from a different action serialization than the `action` argument — e.g. keying on a pre-canonicalization action buffer, mutating the action after key derivation, or copying a key from another entry.","commonSituations":"Custom cache tooling computing keys with a different hashing/serialization than CacheDigest::blake3; refactoring that changes action bytes without recomputing the key; reusing stale keys after retrying with modified task inputs.","solutions":["Recompute the key as CacheDigest::blake3(action).hash immediately before calling commit and pass that value","Never mutate the action buffer after deriving its key","Ensure the same canonical serialization is used for both key derivation and the bytes passed to commit","Log both key and digest on mismatch to identify which side is stale"],"exampleFix":"// before\nlet key = old_key;\naction.inputs.push(extra_input); // action mutated after key derivation\nstore.commit(&key, &action, &manifest, true)?;\n// after\nlet key = CacheDigest::blake3(&action).hash;\nstore.commit(&key, &action, &manifest, true)?;","handlingStrategy":"validation","validationCode":"fn assert_key_matches(key: &CacheKey, action: &[u8]) -> anyhow::Result<()> {\n    let digest = CacheDigest::blake3(action);\n    anyhow::ensure!(digest.hash == *key, \"key {} != action digest {}\", key, digest.hash);\n    Ok(())\n}\n// call immediately before store.commit(&key, &action, ...)","typeGuard":null,"tryCatchPattern":"if let Err(e) = store.commit(&key, &action, &manifest_bytes, has_artifact).await {\n    if e.to_string().contains(\"do not match cache key\") {\n        let key = CacheDigest::blake3(&action).hash;\n        return store.commit(&key, &action, &manifest_bytes, has_artifact).await;\n    }\n    return Err(e);\n}","preventionTips":["Derive the key from the exact bytes passed to commit, at the last moment","Never mutate action buffers after key derivation","Use one shared helper for key computation across all commit call sites"],"tags":["cache","hashing","integrity","content-addressed-storage"],"backgroundTag":"checksum-mismatch","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}