astrid-runtime/astrid · critical

capsule '{}' hash mismatch: signed lock has {}, resolved art

Error message

capsule '{}' hash mismatch: signed lock has {}, resolved artifact has {actual}

What it means

After resolving a signed Distro member to an archive, resolve_signed_capsules hashes the artifact (manifest_hash) and compares it with the hash recorded in the signed lock. On mismatch the artifact is deleted (quarantined) and this error is thrown, since a differing hash means the archive was tampered with, rebuilt, or the signed lock is stale.

Source

Thrown at crates/astrid-cli/src/commands/init_signed_source.rs:136

            }
            let _ = Some(
                super::super::capsule::install::resolve_capsule_to_file(
                    &capsule.source,
                    (!capsule.version.is_empty()).then_some(capsule.version.as_str()),
                    pinned_tag,
                    Some(&capsule.name),
                    &archive_path,
                )
                .await?,
            );
        }
        let bytes = std::fs::read(&archive_path)
            .with_context(|| format!("read resolved capsule {}", capsule.name))?;
        let actual = manifest_hash(&bytes);
        if signed.hash != actual {
            std::fs::remove_file(&archive_path)
                .with_context(|| format!("discard hash-mismatched capsule {}", capsule.name))?;
            anyhow::bail!(
                "capsule '{}' hash mismatch: signed lock has {}, resolved artifact has {actual}",
                capsule.name,
                signed.hash
            );
        }
        let mut member = capsule.clone();
        member.source = archive_path.to_string_lossy().into_owned();
        resolved.push(member);
    }
    Ok(resolved)
}

/// Resolve a distro source to its exact bytes and parse those bytes once.
async fn fetch_manifest_bytes(
    source: &str,
    offline: bool,
) -> anyhow::Result<(Vec<u8>, DistroManifest)> {
    let path = Path::new(source);

View on GitHub (pinned to affd8760f4)

Solutions

  1. Rebuild the capsule and regenerate/re-sign the Distro lock so signed.hash matches the new artifact.
  2. Re-download the .capsule archive from the trusted source in case the local artifact is corrupted.
  3. Compare the two printed hashes to determine whether the lock or the artifact is outdated, and update the stale one.

Example fix

// before: lock signed against old artifact
signed.hash = "abc123..."; artifact hash = "def456..."  // bail

// after: re-sign against rebuilt artifact
astrid distro sign --lock Distro.lock.toml  # updates signed.hash to def456...
Defensive patterns

Strategy: validation

Validate before calling

# verify artifact hash against signed lock before install
sha256sum dist/my-tool.capsule
grep 'hash' Distro.lock.toml  # must match the signed value

Try / catch

// shell
if ! astrid init --signed ./Distro.toml; then
  case $? in
    *) echo "hash mismatch: re-download artifact or re-sign lock, never bypass";;
  esac
fi

Prevention

When it happens

Trigger: resolve_signed_capsules reads the resolved .capsule archive bytes, computes manifest_hash(&bytes), and it differs from signed.hash; the mismatched file is removed and the bail fires with both hashes.

Common situations: Rebuilding a capsule locally without updating the signed lock; a registry serving a different artifact version than the one that was signed; corrupted download; intentional supply-chain tampering attempt.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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