{"record":{"id":"ce3c9ff7ad996161","repo":"astrid-runtime/astrid","slug":"cannot-replace-capsule-id-for-retiring-princip","errorCode":null,"errorMessage":"cannot replace capsule '{id}' for retiring principal '{principal}'","messagePattern":"cannot replace capsule '(.+?)' for retiring principal '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":2151,"sourceCode":"            (source_dir, runtime_id)\n        };\n\n        // Prepare and prove a route-gated replacement while the current\n        // generation remains visible and healthy. A preparation or readiness\n        // failure leaves the running view untouched.\n        let mut prepared = self\n            .prepare_runtime_replacement(id, &source_dir, principal, current_runtime.key().scope())\n            .await?;\n\n        let load_guard = self.capsule_load_lock.lock().await;\n        if self.capabilities.is_principal_retiring(principal).await {\n            drop(load_guard);\n            prepared.capsule.retire();\n            prepared.capsule.request_cancel();\n            if let Err(cleanup) = prepared.capsule.unload().await {\n                tracing::warn!(capsule_id = %id, %cleanup, \"Failed to unload replacement rejected by principal retirement\");\n            }\n            anyhow::bail!(\"cannot replace capsule '{id}' for retiring principal '{principal}'\");\n        }\n        let (mut previous, replacement) = {\n            let mut registry = self.capsules.write().await;\n            if registry.runtime_id_for(principal, id).as_ref() != Some(&current_runtime) {\n                drop(registry);\n                drop(load_guard);\n                prepared.capsule.request_cancel();\n                prepared.capsule.unload().await?;\n                return Ok(RestartOutcome::Superseded);\n            }\n            if prepared.system_runtime\n                && let Err(error) = registry.validate_system_runtime_replacement(\n                    &current_runtime,\n                    prepared.capsule.as_ref(),\n                    &prepared.runtime_id,\n                )\n            {\n                drop(registry);","sourceCodeStart":2133,"sourceCodeEnd":2169,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L2133-L2169","documentation":"During live replacement, after the replacement has been prepared but before it is swapped in, the kernel re-checks principal retirement. If the principal began retiring while the replacement was being prepared, the freshly built replacement is unloaded/cancelled and the operation is rejected, preventing new runtimes from outliving a shutting-down principal.","triggerScenarios":"Calling the replace API for a capsule owned by a principal that enters a retiring state between replacement preparation and the final swap (the retirement check races with the long-running prepare step).","commonSituations":"A slow build/prepare during which an operator disables the principal; automated retirement triggered concurrently with a deployment-triggered hot replace.","solutions":["Complete or cancel the principal's retirement, then retry the replacement.","Check `capabilities.is_principal_retiring(principal)` before starting a replacement and abort early.","Perform replacements under the default principal when the capsule is not principal-specific."],"exampleFix":"// before\nkernel.replace_capsule(id, &principal, source_dir).await?;\n// after\nif capabilities.is_principal_retiring(&principal).await {\n    eprintln!(\"principal retiring; deferring replacement\");\n} else {\n    kernel.replace_capsule(id, &principal, source_dir).await?;\n}","handlingStrategy":"validation","validationCode":"if kernel.capabilities().is_principal_retiring(&principal).await {\n    return Err(anyhow!(\"defer replacement: principal retiring\"));\n}","typeGuard":null,"tryCatchPattern":"match kernel.replace_capsule(id, &principal, &source_dir).await {\n    Err(e) if e.to_string().contains(\"retiring principal\") => schedule_after_retirement(id, principal),\n    other => other,\n}","preventionTips":["Re-check retirement immediately before swap-in, and after long prepares.","Quiesce deployment jobs while retirements are in flight.","Keep replacement prepare steps short to narrow the race window."],"tags":["capsule-lifecycle","principal-retirement","race-condition","live-replacement"],"backgroundTag":"invalid-state-transition","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}