jdx/mise · error · eyre::Report

cannot record a hit for a missing action result

Error message

cannot record a hit for a missing action result

What it means

record_action_hit requires the action result to exist locally (actions.find) or to still sit in pending_remote_actions (speculative prefetch results awaiting first use). If neither holds, the client is claiming a hit for an action whose result the agent never served or prefetched, which is a protocol violation.

Source

Thrown at crates/mise-cache-core/src/agent.rs:1612

    ) -> Result<Option<RemoteActionResult>> {
        self.stats
            .remote_action_lookups
            .fetch_add(1, Ordering::Relaxed);
        let _timer = AtomicDurationTimer::start(&self.stats.remote_action_lookup_duration_ns);
        remote.get_action_result(action).await
    }

    fn record_action_hit(
        &self,
        action: &CacheDigest,
        restore: RestoreStats,
    ) -> Result<AgentResponse> {
        if self.actions.find(action)?.is_none() {
            let pending = self.pending_remote_actions.lock().unwrap().remove(action);
            if let Some(result) = pending {
                self.actions.store(&result)?;
            } else {
                bail!("cannot record a hit for a missing action result");
            }
        }
        self.record_restore(restore);
        self.stats.hits.fetch_add(1, Ordering::Relaxed);
        Ok(AgentResponse::ActionHitRecorded)
    }

    fn record_restore(&self, restore: RestoreStats) {
        self.record_materialization(restore);
        atomic_saturating_add(&self.stats.restored_output_files, restore.output_files);
        atomic_saturating_add(&self.stats.restored_output_bytes, restore.output_bytes);
    }

    fn record_materialization(&self, restore: RestoreStats) {
        atomic_saturating_add(&self.stats.materialization_duration_ns, restore.duration_ns);
    }

    fn find_action_prediction(

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Only record a hit for digests previously returned by the agent (lookup or prefetch responses)
  2. De-duplicate RecordActionHit requests client-side — never send the same digest twice
  3. If pruning races are expected, re-run the lookup path instead of blindly recording a hit
Defensive patterns

Strategy: validation

Validate before calling

// Track served digests client-side and only record hits for them
let mut served: HashSet<[u8; 32]> = HashSet::new();
if let Some(hit) = agent_lookup(&agent, &digest).await? {
    served.insert(digest);
    // ... later ...
    if served.contains(&digest) {
        agent.record_action_hit(&digest, restore_stats).await?;
    }
}

Prevention

When it happens

Trigger: A client sends RecordActionHit for a digest it never received from a lookup/prefetch response; the same digest is recorded twice after the pending entry was consumed; the local cache entry was pruned between serve and record.

Common situations: Hand-rolled clients speculatively reporting hits; concurrent cache pruning racing a hit record; duplicated request replay in client logic.

Related errors


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