{"record":{"id":"2c8efa690dacf3b5","repo":"astrid-runtime/astrid","slug":"principal-capsule-removal-must-go-through-the-auth","errorCode":null,"errorMessage":"principal capsule removal must go through the authenticated daemon","messagePattern":"principal capsule removal must go through the authenticated daemon","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/capsule/remove.rs","lineNumber":30,"sourceCode":"use astrid_core::PrincipalId;\nuse astrid_core::dirs::AstridHome;\n\nuse super::meta::CapsuleMeta;\n\n/// Remove an installed capsule by name.\n///\n/// The caller must run [`validate_capsule_removal`] before any live unload and\n/// this on-disk deletion. Keeping deletion validation-free avoids a second\n/// dependency scan after daemon unload and prevents a stale second snapshot\n/// from disagreeing with the already-authorized operation.\npub(crate) fn remove_capsule(\n    name: &str,\n    workspace: bool,\n    force: bool,\n    purge: bool,\n) -> anyhow::Result<()> {\n    if !workspace {\n        bail!(\"principal capsule removal must go through the authenticated daemon\");\n    }\n    let home = AstridHome::resolve()?;\n    let principal = crate::principal::current();\n    remove_capsule_from_home_for(&home, &principal, name, workspace, force, purge)\n}\n\nfn remove_capsule_from_home(\n    home: &AstridHome,\n    name: &str,\n    workspace: bool,\n    force: bool,\n    purge: bool,\n) -> anyhow::Result<()> {\n    let principal = astrid_capsule_install::paths::install_principal();\n    remove_capsule_from_home_for(home, &principal, name, workspace, force, purge)\n}\n\nfn remove_capsule_from_home_for(","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/capsule/remove.rs#L12-L48","documentation":"remove_capsule refuses to remove a capsule that is not scoped to the workspace: only workspace capsules may be removed directly by the CLI. Principal (user-level) capsules are managed by the authenticated daemon, so the CLI blocks local removal to avoid bypassing daemon-side authentication and state. This is an intentional guard, not a failure of the removal itself.","triggerScenarios":"Calling capsule remove without the workspace flag (workspace == false), so the code takes the principal-capsule path and bails immediately.","commonSituations":"Users omitting --workspace when the capsule they see is actually a workspace capsule registered differently; scripting removals against principal capsules; migration from older CLIs that allowed local principal removal.","solutions":["If the capsule lives in the current workspace, re-run with the workspace flag: astrid capsule remove <name> --workspace.","For principal capsules, perform the removal through the authenticated daemon path (daemon-managed remove command) instead of the CLI-local path.","Verify with capsule list which scope the capsule belongs to before removing."],"exampleFix":"// before\nremove_capsule(name, false, force, purge).await?\n// after\nremove_capsule(name, true, force, purge).await?  // workspace-scoped removal","handlingStrategy":"validation","validationCode":"// pick the right removal path up front\nlet is_workspace_capsule = workspace_root.join(\"capsules\").join(name).exists();\nif !is_workspace_capsule {\n    eprintln!(\"principal capsule: route removal through the authenticated daemon\");\n}","typeGuard":"fn requires_daemon_removal(workspace: bool) -> bool { !workspace }","tryCatchPattern":"if let Err(e) = remove_capsule(...).await {\n    if e.to_string().contains(\"authenticated daemon\") {\n        // switch to the daemon-managed removal command\n    } else { return Err(e); }\n}","preventionTips":["Always pass --workspace for workspace-scoped capsules.","Check capsule list scope before removal.","Script daemon-scope removals via the authenticated daemon API, not the local path."],"tags":["cli","permissions","daemon"],"backgroundTag":"unsupported-operation","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"}