astrid-runtime/astrid · error
remove durable capsule package '{id}': {error}
Error message
remove durable capsule package '{id}': {error} What it means
Thrown in `remove_one_capsule` when `store.capsules().remove(&owner, id)` returns an `Err` after the live view was already unloaded. Before returning the error the kernel calls `ensure_principal_loaded(principal)` to restore the runtime view, since the durable package is still authoritative and must not be left unloaded.
Source
Thrown at crates/astrid-kernel/src/lib.rs:3273
.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
// durable package is still authoritative; restore the just-closed
// runtime view before surfacing the conflict.
self.ensure_principal_loaded(principal).await;
return Err(anyhow::anyhow!(
"durable capsule package '{id}' disappeared during removal"
));
}
Ok(true)
}
#[cfg(target_family = "wasm")]
pub(crate) async fn remove_one_capsule(View on GitHub (pinned to affd8760f4)
Solutions
- Read the wrapped backend error to identify the write failure; fix storage health/permissions and retry.
- Avoid concurrent admin mutations of the same capsule; serialize removals per principal.
- Verify the live view was restored (ensure_principal_loaded ran) before retrying, to avoid a runtime without its registry source.
Example fix
// before: remove while another admin task mutates the same capsule join!(admin.remove(id), other.remove(id)); // after: serialize per principal let _guard = removal_lock_for(&principal).lock().await; admin.remove_one_capsule(&id, &principal).await?;
Defensive patterns
Strategy: retry
Validate before calling
fn store_writable(store: &Store) -> bool { !store.is_read_only() && store.health_check().is_ok() } Try / catch
match kernel.remove_one_capsule(&id, &principal).await {
Err(e) if is_transient_write_error(&e) => {
// live view was restored by the kernel; safe to retry after backoff
tokio::time::sleep(BACKOFF).await;
},
other => other?,
} Prevention
- Serialize administrative mutations per principal/capsule with a lock.
- Confirm storage replicas accept writes before scheduling deletes.
- After a failure, verify the live view is reloaded before retrying.
When it happens
Trigger: The durable delete mutation fails: storage backend write error, generation check rejects the delete, lock contention, or the record changed state between the snapshot read and the remove call.
Common situations: Concurrent administrative operations on the same capsule; storage backend transient failure during write; read-only or degraded replica receiving the delete.
Related errors
- authoritative principal store is unavailable
- resolve durable owner for {principal}: {error}
- read durable capsule package '{id}': {error}
- durable capsule package '{id}' disappeared during removal
- an incomplete capsule authority update exists at {}; remove
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/bcf3aa85d4622bea.
Report an issue: GitHub.