astrid-runtime/astrid · error

read durable capsule package '{id}': {error}

Error message

read durable capsule package '{id}': {error}

What it means

Thrown in `remove_one_capsule` when `store.capsules().get_snapshot(&owner, id)` returns an `Err`. This is a read failure against the durable capsule registry — the snapshot lookup that must succeed before unload/delete could not be performed, so removal aborts before touching the live runtime.

Source

Thrown at crates/astrid-kernel/src/lib.rs:3261

    #[cfg(not(target_family = "wasm"))]
    pub(crate) async fn remove_one_capsule(
        &self,
        id: &astrid_capsule_types::CapsuleId,
        principal: &PrincipalId,
    ) -> Result<bool, anyhow::Error> {
        let store = self
            .principal_store
            .clone()
            .ok_or_else(|| anyhow::anyhow!("authoritative principal store is unavailable"))?;
        let uid = self
            .principal_directory
            .uid_for(principal)
            .map_err(|error| anyhow::anyhow!("resolve durable owner for {principal}: {error}"))?;
        let owner = astrid_storage::StateOwner::Principal(uid);
        let snapshot = store
            .capsules()
            .get_snapshot(&owner, id.as_str())
            .map_err(|error| anyhow::anyhow!("read durable capsule package '{id}': {error}"))?;
        if snapshot.is_none() {
            return Ok(false);
        }
        // Quiesce and unload before deleting the durable package. If unload
        // fails, the package remains authoritative and can be retried on the
        // next request; no live runtime is left without its registry source.
        let _ = self.unload_one_capsule(id, principal).await?;
        let removed = match store.capsules().remove(&owner, id.as_str()) {
            Ok(removed) => removed,
            Err(error) => {
                self.ensure_principal_loaded(principal).await;
                return Err(anyhow::anyhow!(
                    "remove durable capsule package '{id}': {error}"
                ));
            },
        };
        if !removed {
            // A concurrent administrative writer won the generation race. The

View on GitHub (pinned to affd8760f4)

Solutions

  1. Inspect the wrapped source error for the storage backend root cause (I/O, permissions, corruption).
  2. Check storage backend health and disk space; retry the removal after restoring service.
  3. If the record is corrupt, repair or clear the durable capsule package with storage-level tooling, then retry.

Example fix

// before: blind retry loop
loop { kernel.remove_one_capsule(&id, &principal).await?; }
// after: inspect cause, back off on transient backend errors
match kernel.remove_one_capsule(&id, &principal).await {
    Ok(_) => {},
    Err(e) if is_transient(&e) => tokio::time::sleep(BACKOFF).await,
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Validate before calling

fn store_reachable(store: &Store) -> bool { store.health_check().is_ok() }

Try / catch

match kernel.remove_one_capsule(&id, &principal).await {
    Err(e) if is_transient(&e) && attempts < 3 => {
        tokio::time::sleep(backoff).await; // retry
    },
    other => other?,
}

Prevention

When it happens

Trigger: The underlying storage backend errors while reading the capsule record: corrupted store, I/O failure, lock contention, backend unreachable, or a serialization error decoding the snapshot record for `{owner}/{id}`.

Common situations: Disk full or failing on the storage volume; a partially-written capsule package from a crash; storage service restart during an admin delete; schema change making an old snapshot record undecodable.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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