jdx/mise · error · eyre::Report
local action key already has a different result
Error message
local action key already has a different result
What it means
`LocalActionCache::store` is append-once per action key: if a valid result already exists under the same digest and differs from the new one, the store aborts. An identical result is an idempotent no-op, and an invalid existing entry is replaced - only a different, valid result conflicts. Since blake3 collisions are practically impossible, a conflict almost always means the action key derivation is not deterministic: two genuinely different actions hash to the same key.
Source
Thrown at crates/mise-cache-core/src/local.rs:224
for digest in [
Some(&result.action),
result.metadata.as_ref(),
result.output_root.as_ref(),
]
.into_iter()
.flatten()
{
if self.cas.find(digest)?.is_none() {
bail!("cannot publish an action result with a missing blob");
}
}
let destination = self.path_for(&result.action)?;
let replace_invalid = match self.find(&result.action) {
Ok(Some(existing)) => {
if existing == *result {
return Ok(destination);
}
bail!("local action key already has a different result");
}
Ok(None) => false,
Err(_) => true,
};
let parent = destination
.parent()
.expect("action-result path has a parent");
fs::create_dir_all(parent)?;
let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
temporary.write_all(&canonical_json(result)?)?;
temporary.flush()?;
temporary.as_file().sync_all()?;
if replace_invalid {
temporary
.persist(&destination)
.map_err(|error| error.error)?;
return Ok(destination);
}View on GitHub (pinned to 6f52dcdf99)
Solutions
- Make the action key cover every input that can change the result: command line, environment, tool version, platform.
- If the stored entry is stale or wrong, delete the JSON at `action-results/v1/blake3/<xx>/<hash>-<size>.json` and re-store.
- Compare the existing and new results field by field (metadata/output_root digests usually diverge) to identify which input was not hashed.
- Never reuse one action digest for parameterized actions.
Example fix
// before: one digest for parameterized actions let action = CacheDigest::blake3(cmd.as_bytes()); // after: include every input that changes the result let action = CacheDigest::blake3(&canonical_action_key(cmd, &env, &tool_version, platform));
Defensive patterns
Strategy: try-catch
Try / catch
match actions.store(&result) {
Ok(path) => Ok(path),
Err(err) if err.to_string().contains("already has a different result") => {
// keep the existing entry; the conflict is deterministic, do not retry
warn!("action key collision: action digest does not cover all inputs");
Ok(existing_path)
}
Err(err) => Err(err),
} Prevention
- Hash every input that can change the result into the action key: command, env, tool version, platform.
- Never reuse one action digest for parameterized actions.
- Investigate the first conflict immediately - later conflicts get cached under the wrong key.
When it happens
Trigger: Two runs of the same action digest producing different `RemoteActionResult`s, typically because the action key was computed from a subset of the real inputs (tool version, env vars, flags, or platform not included in the hash), while metadata or output_root digests vary between runs.
Common situations: Non-hermetic tasks whose outputs differ; action keys hashed from display strings, paths, or timestamps; one digest reused for parameterized actions.
Related errors
- local action keys must use blake3
- local action result is invalid: {}
- unsupported local action result version
- cannot publish an action result with a missing blob
- local CAS blob failed digest verification: {}
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/fc4e2ee115f9b9b7.
Report an issue: GitHub.