{"record":{"id":"1c22b18ce9ba745a","repo":"jdx/mise","slug":"remote-action-result-is-missing-its-output-root","errorCode":null,"errorMessage":"remote action result is missing its output root","messagePattern":"remote action result is missing its output root","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":418,"sourceCode":"        let metadata_bytes = self\n            .client\n            .get_blob(metadata, CLIENT_METADATA_MEDIA_TYPE)\n            .await?;\n        let metadata: RemoteClientMetadata = serde_json::from_slice(&metadata_bytes)?;\n        if canonical_json(&serde_json::to_value(&metadata)?)? != metadata_bytes {\n            bail!(\"remote cache client metadata is not canonical JSON\");\n        }\n        let mut manifest = metadata.into_manifest(key)?;\n        for root in &manifest.roots {\n            validate_cache_path(root)?;\n        }\n        let artifact = match &result.output_root {\n            Some(root) => {\n                let temporary = materialize_remote_tree(self, root).await?;\n                Some(TaskCacheStoreArtifact::temporary(temporary))\n            }\n            None if manifest.roots.is_empty() => None,\n            None => bail!(\"remote action result is missing its output root\"),\n        };\n        manifest.artifact_checksum = Some(calculate_artifact_checksum(\n            &manifest,\n            artifact.as_ref().map(TaskCacheStoreArtifact::path),\n        )?);\n        let manifest = serde_json::to_vec(&manifest)?;\n        Ok(Some(TaskCacheStoreEntry { manifest, artifact }))\n    }\n\n    fn begin_write(&self, key: &str) -> Result<TaskCacheStoreWrite> {\n        validate_remote_key(key)?;\n        file::create_dir_all(&self.staging_dir)?;\n        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    }","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/task_cache_store.rs#L400-L436","documentation":"HttpTaskCacheStore::get fetched a remote action result for a cache key, and the result declared output roots in its manifest but carried no output_root tree blob. The store refuses to fabricate an empty artifact when the manifest says outputs exist. This indicates a truncated, corrupt, or inconsistently written remote cache entry.","triggerScenarios":"Calling get() on the HTTP remote task cache store when the RemoteActionResult has output_root == None while the decoded manifest.roots is non-empty (task_cache_store.rs:418).","commonSituations":"A remote cache server (e.g. a CAS proxy) that drops or never stored the tree blob; a partially failed publish that wrote the action result before the output root; a misbehaving non-mise writer populating the remote cache; manual deletion of tree blobs server-side.","solutions":["Re-run the task so it re-executes and republishes a complete remote cache entry with its output root.","Purge the corrupt cache key from the remote cache server so get() returns a miss instead of an inconsistent entry.","Verify the remote cache backend preserves tree blobs and writes the action result only after all blobs are uploaded.","Check for a version-skewed client writing results in an incompatible format and align client versions."],"exampleFix":"// Corrupt remote entry; force a fresh write\n$ mise run build --force   # re-executes task and repopulates remote cache\n// or server-side\n$ curl -X DELETE https://cache.example.com/cas/<output-root-digest>","handlingStrategy":"fallback","validationCode":"// Caller-side: treat fetch failure as a cache miss and rebuild\nmatch store.get(&key, action_size).await {\n    Ok(Some(entry)) => entry,\n    _ => execute_task_and_republish().await?,\n}","typeGuard":"fn has_output_root(result: &RemoteActionResult, manifest_roots: &[PathBuf]) -> bool {\n    result.output_root.is_some() || manifest_roots.is_empty()\n}","tryCatchPattern":"let entry = match store.get(&key, size).await {\n    Ok(e) => e,\n    Err(e) if e.to_string().contains(\"missing its output root\") => {\n        log::warn!(\"inconsistent remote entry for {key}; rebuilding\");\n        None\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Ensure the remote cache server publishes the action result only after all tree blobs are stored.","Treat remote-cache read errors as cache misses and rebuild rather than hard-failing.","Monitor the cache server for deleted/truncated tree blobs.","Keep all writers on the same client version."],"tags":["cache","remote","integrity","missing-data"],"backgroundTag":"unexpected-response-shape","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}