jdx/mise · error

task action manifest contains duplicate predictions

Error message

task action manifest contains duplicate predictions

What it means

Thrown by validate_task_manifest (crates/mise-cache-core/src/agent.rs:1831) when two predictions in the same manifest share an identical invocation value. The validator inserts each prediction.invocation into a BTreeMap and bails if the key was already present, because the cache resolves predictions by invocation — a duplicate would make the lookup ambiguous.

Source

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

    if prediction.payload.len() > MAX_ACTION_PREDICTION_PAYLOAD {
        bail!("action prediction payload is too large");
    }
    serde_json::from_str::<serde_json::Value>(&prediction.payload)?;
    Ok(())
}

fn validate_task_manifest(manifest: &TaskActionManifest, task: &str) -> Result<()> {
    if manifest.version != TASK_ACTION_MANIFEST_VERSION || manifest.task != task {
        bail!("task action manifest has an invalid identity");
    }
    if manifest.predictions.len() > MAX_TASK_ACTION_PREDICTIONS {
        bail!("task action manifest contains too many predictions");
    }
    let mut invocations = BTreeMap::new();
    for prediction in &manifest.predictions {
        validate_action_prediction(prediction)?;
        if invocations.insert(&prediction.invocation, ()).is_some() {
            bail!("task action manifest contains duplicate predictions");
        }
    }
    Ok(())
}

fn merge_task_manifests(
    task: &str,
    base: Option<TaskActionManifest>,
    update: TaskActionManifest,
) -> Result<TaskActionManifest> {
    validate_task_manifest(&update, task)?;
    let mut predictions = BTreeMap::new();
    if let Some(base) = base {
        validate_task_manifest(&base, task)?;
        predictions.extend(
            base.predictions
                .into_iter()
                .map(|prediction| (prediction.invocation.clone(), prediction)),

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Deduplicate the predictions array by invocation before persisting/merging (keep the newest payload per invocation)
  2. Fix the producer so it never emits two predictions for one invocation — treat an existing invocation as an update, not an append
  3. Inspect the duplicate invocations: if many are empty strings, the invocation extraction upstream is broken; fix that instead of deduping the symptom
  4. Regenerate the manifest after the fix so the stored file no longer contains duplicates

Example fix

// before
let mut predictions = old_manifest.predictions;
predictions.extend(new_manifest.predictions); // may duplicate invocations

// after
let mut by_invocation: BTreeMap<String, ActionPrediction> = old_manifest.predictions.into_iter().map(|p| (p.invocation.clone(), p)).collect();
by_invocation.extend(new_manifest.predictions.into_iter().map(|p| (p.invocation.clone(), p)));
let predictions = by_invocation.into_values().collect();
Defensive patterns

Strategy: validation

Validate before calling

use std::collections::BTreeMap;

fn dedupe_predictions(predictions: Vec<ActionPrediction>) -> Vec<ActionPrediction> {
    predictions
        .into_iter()
        .map(|p| (p.invocation.clone(), p))
        .collect::<BTreeMap<_, _>>()
        .into_values()
        .collect()
}
// run before persisting/merging

Type guard

fn has_unique_invocations(m: &TaskActionManifest) -> bool {
    let mut seen = std::collections::BTreeSet::new();
    m.predictions.iter().all(|p| seen.insert(p.invocation.clone()))
}

Try / catch

match merge_task_manifests(task, base, update) {
    Err(e) if e.to_string().contains("duplicate predictions") => {
        let deduped = dedupe_predictions(update.predictions);
        merge_task_manifests(task, base, TaskActionManifest { predictions: deduped, ..update })
    }
    other => other,
}

Prevention

When it happens

Trigger: Passing a manifest to merge_task_manifests (agent.rs:1836) where predictions[i].invocation == predictions[j].invocation for some i != j. Happens when a producer appends predictions for the same command line twice (e.g. merging overlapping runs without keyed dedup, or generating one entry per source file where several produce the identical invocation string).

Common situations: Manual manifest construction or a generator loop that emits a default/empty invocation for entries it could not classify; merging two manifests that both contain a common invocation before deduplicating; unstable serialization that collapses distinct invocations to the same string.

Related errors


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