astrid-runtime/astrid · error

Distro.lock capsule '{capsule}' has an invalid BLAKE3 hash

Error message

Distro.lock capsule '{capsule}' has an invalid BLAKE3 hash

What it means

The lock's WASM hash string must parse as a BLAKE3 hash after the `blake3:` prefix. `blake3::Hash::from_hex` failed, so the value after the prefix is not valid hexadecimal of the right length. The library refuses to validate the capsule because it cannot even parse the pinned hash, distinguishing this from the separate canonical-form (lowercase, 64 chars) check.

Source

Thrown at crates/astrid-cli/src/commands/init_grant.rs:429

                "Distro.lock capsule '{}' content blob is missing or unreadable at {}",
                capsule,
                blob_path.display()
            )
        })?
    };
    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

View on GitHub (pinned to affd8760f4)

Solutions

  1. Regenerate Distro.lock so it contains the correct `blake3:<64-hex>` hash from the built WASM
  2. Compute the correct hash with `b3sum <capsule>.wasm` and paste it (keeping the `blake3:` prefix)
  3. Restore the lock from the signed source (`Distro.lock` fetch) instead of editing locally
  4. Ensure any tooling writing the lock emits lowercase 64-character hex

Example fix

# before
wasm = "blake3:abc123"
# after
wasm = "blake3:2f1e..."  # full 64-char lowercase hex from b3sum
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_blake3_lock(value: &str) -> bool {
    value.strip_prefix("blake3:")
        .map(|h| h.len() == 64 && h.bytes().all(|b| b.is_ascii_hexdigit()))
        .unwrap_or(false)
}

Type guard

fn canonical_blake3(value: &str) -> Option<blake3::Hash> {
    let hex = value.strip_prefix("blake3:")?;
    let hash = blake3::Hash::from_hex(hex).ok()?;
    (hex.len() == 64 && hash.to_hex().as_str() == hex).then_some(hash)
}

Try / catch

match res {
    Err(e) if e.to_string().contains("invalid BLAKE3 hash") => regenerate_lock(),
    other => other,
}

Prevention

When it happens

Trigger: `parse_locked_blake3` receives a value like `blake3:xyz...` where the hex part contains invalid characters or wrong length, making `from_hex` return Err. Any hand-edited or programmatically generated lock with a malformed hash triggers this.

Common situations: Hand-editing Distro.lock and mistyping the hash; using an MD5/SHA-256 hex string instead of BLAKE3; truncating the hash when copying; generating the lock with a script that writes a placeholder value.

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


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