jdx/mise · error

remote action result is missing its output root

Error message

remote action result is missing its output root

What it means

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.

Solutions

  1. Re-run the task so it re-executes and republishes a complete remote cache entry with its output root.
  2. Purge the corrupt cache key from the remote cache server so get() returns a miss instead of an inconsistent entry.
  3. Verify the remote cache backend preserves tree blobs and writes the action result only after all blobs are uploaded.
  4. Check for a version-skewed client writing results in an incompatible format and align client versions.

Example fix

// Corrupt remote entry; force a fresh write
$ mise run build --force   # re-executes task and repopulates remote cache
// or server-side
$ curl -X DELETE https://cache.example.com/cas/<output-root-digest>
Defensive patterns

Strategy: fallback

Validate before calling

// Caller-side: treat fetch failure as a cache miss and rebuild
match store.get(&key, action_size).await {
    Ok(Some(entry)) => entry,
    _ => execute_task_and_republish().await?,
}

Type guard

fn has_output_root(result: &RemoteActionResult, manifest_roots: &[PathBuf]) -> bool {
    result.output_root.is_some() || manifest_roots.is_empty()
}

Try / catch

let entry = match store.get(&key, size).await {
    Ok(e) => e,
    Err(e) if e.to_string().contains("missing its output root") => {
        log::warn!("inconsistent remote entry for {key}; rebuilding");
        None
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/1c22b18ce9ba745a. Report an issue: GitHub.

Appendix: source

Thrown at src/task/task_cache_store.rs:418

        let metadata_bytes = self
            .client
            .get_blob(metadata, CLIENT_METADATA_MEDIA_TYPE)
            .await?;
        let metadata: RemoteClientMetadata = serde_json::from_slice(&metadata_bytes)?;
        if canonical_json(&serde_json::to_value(&metadata)?)? != metadata_bytes {
            bail!("remote cache client metadata is not canonical JSON");
        }
        let mut manifest = metadata.into_manifest(key)?;
        for root in &manifest.roots {
            validate_cache_path(root)?;
        }
        let artifact = match &result.output_root {
            Some(root) => {
                let temporary = materialize_remote_tree(self, root).await?;
                Some(TaskCacheStoreArtifact::temporary(temporary))
            }
            None if manifest.roots.is_empty() => None,
            None => bail!("remote action result is missing its output root"),
        };
        manifest.artifact_checksum = Some(calculate_artifact_checksum(
            &manifest,
            artifact.as_ref().map(TaskCacheStoreArtifact::path),
        )?);
        let manifest = serde_json::to_vec(&manifest)?;
        Ok(Some(TaskCacheStoreEntry { manifest, artifact }))
    }

    fn begin_write(&self, key: &str) -> Result<TaskCacheStoreWrite> {
        validate_remote_key(key)?;
        file::create_dir_all(&self.staging_dir)?;
        let nonce = crate::rand::random_string(8);
        Ok(TaskCacheStoreWrite {
            artifact_path: self.staging_dir.join(format!("{key}.part-{nonce}.tar.zst")),
            manifest_path: self.staging_dir.join(format!("{key}.part-{nonce}.json")),
        })
    }

View on GitHub (pinned to 533346cc37)