astrid-runtime/astrid · error
failed to unregister capsule
Error message
failed to unregister capsule '{id}': {e} What it means
Thrown when `registry.unregister_for(principal, id)` fails with an error other than `CapsuleError::NotFound` during capsule uninstall (lib.rs:3153). NotFound is treated as a benign no-op (returns `Ok(false)`), but any other registry error — lock poisoning, invariant violation, unexpected internal state — is wrapped and propagated as a failed unregister.
Solutions
- Inspect the wrapped `{e}` cause (the `CapsuleError` variant) to see why the registry refused the removal
- Ensure no concurrent install/uninstall/restart is racing on the same capsule; serialize operations per capsule
- If the registry entry is stuck, reload or restart the kernel to rebuild registry state, then retry the uninstall
- Retry the uninstall: a transient state may clear once in-flight operations drain
Example fix
// before
let removed = kernel.unregister_capsule(&principal, &id).await?; // Err on non-NotFound
// after
match kernel.unregister_capsule(&principal, &id).await {
Ok(removed) => tracing::info!(removed),
Err(e) => {
// inspect chained CapsuleError; serialize with other capsule ops and retry
kernel.reload_registry_state().await?;
kernel.unregister_capsule(&principal, &id).await?;
},
} Defensive patterns
Strategy: try-catch
Validate before calling
// Only attempt unregister when the capsule is in a removable state
if !kernel.capsule_is_draining(principal, id).await {
kernel.unregister_capsule(principal, id).await?;
} Try / catch
match kernel.unregister_capsule(principal, id).await {
Err(e) => {
// inspect the chained CapsuleError variant; retry once after serializing
serialize_on_capsule_load_lock(principal, id).await;
kernel.unregister_capsule(principal, id).await.ok();
},
ok => ok?,
} Prevention
- Route all install/uninstall through the `capsule_load_lock`-serialized API
- Treat NotFound as success/idempotent so retries are safe
- Avoid concurrent uninstalls of the same capsule from multiple tasks
When it happens
Trigger: Calling the kernel's uninstall/remove API where the registry write path returns a non-NotFound `CapsuleError` (lib.rs:3153), e.g. the registry rejects the removal due to inconsistent internal state or a guarded entry.
Common situations: Uninstalling a capsule while it is in a state the registry forbids removing; concurrent load/unload races despite the `capsule_load_lock`; internal registry corruption after a failed prior operation.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- capsule ' ' not found in registry
- an incomplete capsule authority update exists at
- cannot load capsule ' ' for unadmitted principal
- cannot remove capsule authority while an install…
- capsule ' ' failed to load
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f46c8c65a11f66ee.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/lib.rs:3153
///
/// Returns an error only if the registry fails to unregister a capsule it
/// reported as present.
pub(crate) async fn unload_one_capsule(
&self,
id: &astrid_capsule_types::CapsuleId,
principal: &PrincipalId,
) -> Result<bool, anyhow::Error> {
let _view_guard = self.lock_capsule_view(principal, id).await;
let load_guard = self.capsule_load_lock.lock().await;
// A principal runtime is always torn down. An operator-owned
// `SystemResident` runtime survives until its final view is released.
let removed = {
let mut registry = self.capsules.write().await;
match registry.unregister_for(principal, id) {
Ok(removed) => removed,
Err(astrid_capsule_types::error::CapsuleError::NotFound(_)) => return Ok(false),
Err(e) => {
return Err(anyhow::anyhow!("failed to unregister capsule '{id}': {e}"));
},
}
};
// Registration/reload is serialized by `capsule_load_lock`, so the
// registry map lock can be released before awaiting the old runtime's
// drain. This avoids a lock cycle with admitted host calls that perform
// generation-scoped registry reads.
if removed.torn_down {
// The generation is no longer reachable from the registry. Close
// every admission path before releasing the global publication
// lock, then let generation-owned teardown drain independently.
// This preserves hard MCP process-tree cleanup without allowing a
// wedged child to block unrelated principals' lifecycle work.
removed.capsule.retire();
removed.capsule.request_cancel();
drop(load_guard);
removed.capsule.quiesce_for(principal).await;
} else {View on GitHub (pinned to affd8760f4)