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
- Rebuild the capsule and regenerate/re-sign the Distro lock so signed.hash matches the new artifact.
- Re-download the .capsule archive from the trusted source in case the local artifact is corrupted.
- 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
- Regenerate the signed lock every time a capsule is rebuilt.
- Pin exact capsule versions in Distro.toml so artifacts stay reproducible.
- Never ignore or work around a hash mismatch — treat it as a possible tampering signal.
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
- signed Distro.lock entry '{}' has no capsule hash
- release contains duplicate asset '{name}'
- InvalidData
- opaque capsule asset directories cannot be symlinks: {}
- capsule archive already contains {PROVENANCE_FILE}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/2605eecf93a8cdb7.
Report an issue: GitHub.