astrid-runtime/astrid · error

capsule identity mismatch: expected '{}', manifest declares

Error message

capsule identity mismatch: expected '{}', manifest declares '{id}'

What it means

Inside install_from_local_path_internal, after loading the manifest, the parsed CapsuleId is compared against the optional ExpectedCapsuleIdentity passed by the caller. A mismatch means the directory being installed declares a different capsule name than the one the caller intended/approved, so the install aborts before any copy or store mutation. This duplicates the pre-check at the checked_authorized layer for callers that skip it.

Source

Thrown at crates/astrid-capsule-install/src/local.rs:576

        Some(
            workspace
                .layout
                .resolve(root)
                .context("selected workspace state path is unsafe")?,
        )
    } else {
        None
    };
    let manifest_path = source_dir.join("Capsule.toml");
    if !manifest_path.exists() {
        bail!("No Capsule.toml found in {}", source_dir.display());
    }
    let manifest = load_manifest(&manifest_path).context("failed to load Capsule manifest")?;
    let id = CapsuleId::new(manifest.package.name.clone())?;
    if let Some(expected) = expected
        && id != *expected.id
    {
        bail!(
            "capsule identity mismatch: expected '{}', manifest declares '{id}'",
            expected.id
        );
    }
    let installed_version = manifest.package.version.clone();
    if let Some(expected_version) = expected.and_then(|expected| expected.version)
        && installed_version != expected_version
    {
        bail!(
            "capsule version mismatch for '{id}': expected '{expected_version}', manifest declares '{installed_version}'"
        );
    }

    // Re-verify the exact source immediately before any target mutation. This
    // closes the gap between pre-install approval and the transactional copy,
    // including provenance-envelope swaps that leave content bytes unchanged.
    let installed_authority =
        authority_for_install_source(source_dir, &manifest, installed_authority)?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Read package.name from the source Capsule.toml and make the expected id match exactly
  2. Correct package.name in the manifest if the directory is the source of truth
  3. Point source_dir at the directory of the intended capsule
  4. Drop the expected identity only if you truly want an unchecked install (not recommended for authorized flows)

Example fix

// before
let expected = ExpectedCapsuleIdentity { id: &CapsuleId::new("app-core")?, version: None };
// after
let manifest: Manifest = toml::from_str(&std::fs::read_to_string(src.join("Capsule.toml"))?)?;
let expected = ExpectedCapsuleIdentity { id: &CapsuleId::new(manifest.package.name.clone())?, version: None };
Defensive patterns

Strategy: validation

Validate before calling

fn check_identity(src: &Path, expected_id: &CapsuleId) -> anyhow::Result<()> {
    let manifest: Manifest = toml::from_str(&std::fs::read_to_string(src.join("Capsule.toml"))?)?;
    anyhow::ensure!(CapsuleId::new(manifest.package.name.clone())? == *expected_id, "id drift");
    Ok(())
}

Try / catch

if let Err(e) = install_from_local_path_internal(...) {
    if e.to_string().contains("capsule identity mismatch") {
        // reload manifest, resync expected identity, retry once
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Passing expected = Some(ExpectedCapsuleIdentity { id, .. }) to install_from_local_path_internal (via any wrapper) while the Capsule.toml in source_dir declares a different package.name — e.g. expected 'app-core', manifest says 'app-core-utils'.

Common situations: Renamed capsule without updating callers; wrong source directory after a refactor; case-sensitive name mismatch; reusing an install routine parameterized by id but pointing at a template directory.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/d7f95e5c640c650b. Report an issue: GitHub.