astrid-runtime/astrid · error
Distro.lock capsule '{capsule}' requires a canonical lowerca
Error message
Distro.lock capsule '{capsule}' requires a canonical lowercase BLAKE3 hash What it means
Even a parseable blake3 hash must be in canonical form: exactly 64 hex characters whose lowercase hex round-trips through blake3::Hash::to_hex. Uppercase letters or extra characters are rejected so lockfiles stay byte-comparable and diff-stable.
Source
Thrown at crates/astrid-cli/src/commands/init_grant.rs:432
)
})?
};
let actual = blake3::hash(&bytes);
if actual != locked {
bail!("Distro.lock capsule '{capsule}' content blob bytes do not match hash {locked_hash}");
}
Ok(())
}
fn parse_locked_blake3(capsule: &CapsuleId, value: &str) -> anyhow::Result<blake3::Hash> {
let Some(hex) = value.strip_prefix("blake3:") else {
bail!("Distro.lock capsule '{capsule}' requires a canonical blake3:<hex> WASM hash");
};
let hash = blake3::Hash::from_hex(hex).map_err(|_| {
anyhow::anyhow!("Distro.lock capsule '{capsule}' has an invalid BLAKE3 hash")
})?;
if hex.len() != 64 || hash.to_hex().as_str() != hex {
bail!("Distro.lock capsule '{capsule}' requires a canonical lowercase BLAKE3 hash");
}
Ok(hash)
}
fn manifest_declares_wasm(manifest: &CapsuleManifest) -> bool {
manifest
.components
.iter()
.any(|component| component.path.extension().and_then(|ext| ext.to_str()) == Some("wasm"))
}
/// Apply capsule-access grants for the installed set (opt-in), or print
/// the discoverability hint when the flag was omitted.
///
/// On the grant path the capsules are already installed and the lock is
/// written; a failure here (daemon unreachable, caller lacks `agent:modify`)
/// returns `Err` so `init` exits non-zero, but always prints the exact
/// manual command to finish. The kernel applies the whole `add_capsules`View on GitHub (pinned to affd8760f4)
Solutions
- Lowercase the hex portion of the hash in Distro.lock and confirm it is exactly 64 characters.
- Regenerate Distro.lock with the CLI so it writes the canonical lowercase hash.
- Normalize hashes in any tooling that writes the lockfile (lowercase, no padding).
Example fix
// before (Distro.lock) hash = "blake3:9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08" // after hash = "blake3:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
Defensive patterns
Strategy: validation
Validate before calling
let hex = value.strip_prefix("blake3:")?;
let h = blake3::Hash::from_hex(hex).ok()?;
if hex.len() != 64 || h.to_hex().as_str() != hex { return Err(anyhow!("non-canonical hash")); } Type guard
fn is_canonical_blake3(value: &str) -> bool {
match value.strip_prefix("blake3:") {
Some(h) => h.len() == 64
&& h.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
&& blake3::Hash::from_hex(h).map(|x| x.to_hex().as_str() == h).unwrap_or(false),
None => false,
}
} Try / catch
match parse_locked_blake3(capsule, value) {
Err(e) if e.to_string().contains("canonical lowercase") => normalize_hash_case(capsule)?,
other => other?,
} Prevention
- Lowercase all hex digests before writing lockfiles.
- Add a canonicalization step to any script that emits hashes.
- Run lockfile format checks in CI.
When it happens
Trigger: parse_locked_blake3 parses the hex successfully but hex.len() != 64 or hash.to_hex().as_str() != hex — e.g. uppercase hex, or a hex string with a stray character that still parses.
Common situations: Uppercase hex from another tool's output, padded or trimmed hex from manual editing, script-generated lockfile emitting non-canonical casing.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Distro.lock capsule '{capsule}' requires a canonical blake3:
- Distro.lock capsule '{}' hash disagrees with the daemon regi
- Distro.lock capsule '{capsule}' hash disagrees with installe
- capsule archive already contains {PROVENANCE_FILE}
- Distro.lock capsule '{expected}' resolves to installed manif
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/cc2f6eba94772067.
Report an issue: GitHub.