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

  1. Make the action key cover every input that can change the result: command line, environment, tool version, platform.
  2. If the stored entry is stale or wrong, delete the JSON at `action-results/v1/blake3/<xx>/<hash>-<size>.json` and re-store.
  3. Compare the existing and new results field by field (metadata/output_root digests usually diverge) to identify which input was not hashed.
  4. 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

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


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/fc4e2ee115f9b9b7. Report an issue: GitHub.