{"record":{"id":"f46c8c65a11f66ee","repo":"astrid-runtime/astrid","slug":"failed-to-unregister-capsule-id-e","errorCode":null,"errorMessage":"failed to unregister capsule '{id}': {e}","messagePattern":"failed to unregister capsule '(.+?)': (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":3153,"sourceCode":"    ///\n    /// Returns an error only if the registry fails to unregister a capsule it\n    /// reported as present.\n    pub(crate) async fn unload_one_capsule(\n        &self,\n        id: &astrid_capsule_types::CapsuleId,\n        principal: &PrincipalId,\n    ) -> Result<bool, anyhow::Error> {\n        let _view_guard = self.lock_capsule_view(principal, id).await;\n        let load_guard = self.capsule_load_lock.lock().await;\n        // A principal runtime is always torn down. An operator-owned\n        // `SystemResident` runtime survives until its final view is released.\n        let removed = {\n            let mut registry = self.capsules.write().await;\n            match registry.unregister_for(principal, id) {\n                Ok(removed) => removed,\n                Err(astrid_capsule_types::error::CapsuleError::NotFound(_)) => return Ok(false),\n                Err(e) => {\n                    return Err(anyhow::anyhow!(\"failed to unregister capsule '{id}': {e}\"));\n                },\n            }\n        };\n        // Registration/reload is serialized by `capsule_load_lock`, so the\n        // registry map lock can be released before awaiting the old runtime's\n        // drain. This avoids a lock cycle with admitted host calls that perform\n        // generation-scoped registry reads.\n        if removed.torn_down {\n            // The generation is no longer reachable from the registry. Close\n            // every admission path before releasing the global publication\n            // lock, then let generation-owned teardown drain independently.\n            // This preserves hard MCP process-tree cleanup without allowing a\n            // wedged child to block unrelated principals' lifecycle work.\n            removed.capsule.retire();\n            removed.capsule.request_cancel();\n            drop(load_guard);\n            removed.capsule.quiesce_for(principal).await;\n        } else {","sourceCodeStart":3135,"sourceCodeEnd":3171,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L3135-L3171","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet removed = kernel.unregister_capsule(&principal, &id).await?; // Err on non-NotFound\n// after\nmatch kernel.unregister_capsule(&principal, &id).await {\n    Ok(removed) => tracing::info!(removed),\n    Err(e) => {\n        // inspect chained CapsuleError; serialize with other capsule ops and retry\n        kernel.reload_registry_state().await?;\n        kernel.unregister_capsule(&principal, &id).await?;\n    },\n}","handlingStrategy":"try-catch","validationCode":"// Only attempt unregister when the capsule is in a removable state\nif !kernel.capsule_is_draining(principal, id).await {\n    kernel.unregister_capsule(principal, id).await?;\n}","typeGuard":null,"tryCatchPattern":"match kernel.unregister_capsule(principal, id).await {\n    Err(e) => {\n        // inspect the chained CapsuleError variant; retry once after serializing\n        serialize_on_capsule_load_lock(principal, id).await;\n        kernel.unregister_capsule(principal, id).await.ok();\n    },\n    ok => ok?,\n}","preventionTips":["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"],"tags":["rust","registry","capsule","uninstall"],"backgroundTag":"internal-invariant-violation","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}