astrid-runtime/astrid · critical
manifest hash mismatch: lock records
Error message
manifest hash mismatch: lock records {recorded}, archive Distro.toml hashes to {actual} — the shuttle is inconsistent or tampered What it means
check_manifest_binding recomputes the hash of the archive's Distro.toml and compares it to the manifest_hash recorded in the sealed lock. A mismatch means the lock and the manifest inside the shuttle disagree — either the shuttle was assembled inconsistently or the manifest was swapped/tampered after sealing. The install aborts to guarantee the authenticated lock actually binds to this manifest.
Solutions
- Rebuild the shuttle from a clean build so lock.manifest_hash matches the archive's Distro.toml
- Do not edit Distro.toml after the shuttle is sealed; change the manifest and rebuild
- Re-copy the shuttle if media corruption is suspected and retry
Defensive patterns
Strategy: validation
Validate before calling
let actual = manifest_hash(manifest_bytes);
if lock.manifest_hash.as_deref() != Some(actual.as_str()) {
return Err(anyhow!("manifest hash mismatch: lock vs Distro.toml"));
} Try / catch
match install_from_shuttle(...) {
Err(e) if e.to_string().contains("manifest hash mismatch") => {
// quarantine the shuttle, notify the operator; never retry in place
}
...
} Prevention
- Never edit Distro.toml after sealing; rebuild the shuttle instead
- Keep lock and manifest from the same build output directory
- Re-verify hashes after copying shuttle media
When it happens
Trigger: install_from_shuttle on a shuttle whose Distro.toml was edited after the lock was sealed; a mismatched lock/manifest pair copied from two different distro builds; corruption of either file on the shuttle media.
Common situations: Manually tweaking env/selection in Distro.toml on a prepared shuttle; mixing files from two build attempts onto one shuttle; transport corruption that survives file copy.
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
- capsule ' ' hash mismatch: lock has , archive has
- capsule ' ' is missing from the shuttle mirror
- durable capsule WASM hash differs between metadata and…
- installed capsule ' ' hash disagreement: installer= , meta=
- installed WASM executable differs from its authority receipt
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/0e7ce506a666621e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/distro/shuttle_install.rs:278
/// to that signature. So when the shuttle is `signed`, a `manifest_hash`
/// is mandatory and must match — a `None` is a hard fail (an attacker
/// could otherwise keep a signed lock+sig+pubkey and swap only the
/// manifest, leaving env/selection unauthenticated). For an unsigned
/// install there is no signed lock to bind against, so a `None` is
/// tolerated and a present hash is still checked best-effort.
///
/// Pure (no I/O) so the binding gate is unit-testable.
fn check_manifest_binding(
distro_id: &str,
signed: bool,
lock: &DistroLock,
manifest_bytes: &[u8],
) -> anyhow::Result<()> {
let actual = manifest_hash(manifest_bytes);
match &lock.manifest_hash {
Some(recorded) => {
if recorded != &actual {
bail!(
"manifest hash mismatch: lock records {recorded}, archive Distro.toml hashes \
to {actual} — the shuttle is inconsistent or tampered"
);
}
Ok(())
},
None if signed => bail!(
"signed shuttle for '{distro_id}' is missing its manifest_hash binding — refusing. \
The signature covers the lock, not Distro.toml; without manifest_hash the manifest \
(env/selection) is unauthenticated and could be swapped."
),
None => Ok(()),
}
}
/// Verify the per-capsule blake3 of every lock entry against the bytes
/// actually present in the mirror. Returns an error on the first
/// mismatch or missing file. Pure (no install side effects) so theView on GitHub (pinned to affd8760f4)