astrid-runtime/astrid · error

Distro.lock capsule '{capsule}' catalog entry has no readabl

Error message

Distro.lock capsule '{capsule}' catalog entry has no readable bytes: bin/{locked_hex}.wasm

What it means

After the catalog descriptor exists, `read_range` is expected to return the WASM bytes; when it returns None the catalog entry has no readable bytes. The library needs the bytes to hash-verify the locked WASM, so a descriptor without readable content is treated as a corrupt or inconsistent catalog entry.

Source

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

            .content()
            .describe(&astrid_storage::StateOwner::System, &name)
            .map_err(|error| anyhow::anyhow!(error))?
            .ok_or_else(|| {
                anyhow::anyhow!(
                    "Distro.lock capsule '{capsule}' catalog entry is missing: bin/{locked_hex}.wasm"
                )
            })?;
        store
            .content()
            .read_range(
                &astrid_storage::StateOwner::System,
                &name,
                0,
                descriptor.logical_bytes(),
            )
            .map_err(|error| anyhow::anyhow!(error))?
            .ok_or_else(|| {
                anyhow::anyhow!(
                    "Distro.lock capsule '{capsule}' catalog entry has no readable bytes: bin/{locked_hex}.wasm"
                )
            })?
    } else {
        let blob_path = home.bin_dir().join(format!("{locked_hex}.wasm"));
        std::fs::read(&blob_path).with_context(|| {
            format!(
                "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(())

View on GitHub (pinned to affd8760f4)

Solutions

  1. Re-ingest the WASM content so the backend holds its bytes (re-run publish/sync for the capsule)
  2. Repair the storage backend (restore blob data) or point the config at the correct data directory
  3. Delete the stale catalog entry and re-add the content
  4. Regenerate the lock and re-sync the distro if the stored content no longer matches

Example fix

// before
let bytes = store.content().read_range(...)?  // None -> error
// after
$ astrid distro sync   // restore readable bytes in backend, then retry grant
Defensive patterns

Strategy: validation

Validate before calling

fn catalog_bytes_readable(store: &Store, locked_hex: &str) -> bool {
    let name = astrid_storage::ContentName::new(format!("bin/{locked_hex}.wasm")).unwrap();
    match store.content().describe(&astrid_storage::StateOwner::System, &name) {
        Ok(Some(d)) => store.content().read_range(&name, 0, d.logical_bytes()).map(|b| b.is_some()).unwrap_or(false),
        _ => false,
    }
}

Try / catch

match res {
    Err(e) if e.to_string().contains("no readable bytes") => repair_backend_or_reingest(),
    other => other,
}

Prevention

When it happens

Trigger: `validate_locked_wasm` calls `store.content().read_range(&name, 0, descriptor.logical_bytes())`; the result maps to None, raising this error. This is the non-blob fallback path (store-backed content), meaning describe succeeded but the payload is missing/unreadable in the content backend.

Common situations: Catalog index intact but backing blob storage lost (e.g. disk cleanup, partial deletion); backend desync between index and data store; logical_bytes reported but payload truncated or missing after a failed ingest.

Related errors


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