{"record":{"id":"15bd0fb6f524c910","repo":"astrid-runtime/astrid","slug":"capsule-id-has-no-source-directory","errorCode":null,"errorMessage":"capsule '{id}' has no source directory","messagePattern":"capsule '(.+?)' has no source directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":2132,"sourceCode":"        id: &astrid_capsule_types::CapsuleId,\n        principal: &PrincipalId,\n        expected_runtime: Option<&astrid_capsule::registry::RuntimeId>,\n    ) -> Result<RestartOutcome, anyhow::Error> {\n        let (source_dir, current_runtime) = {\n            let registry = self.capsules.read().await;\n            let capsule = registry\n                .get_for(principal, id)\n                .ok_or_else(|| anyhow::anyhow!(\"capsule '{id}' not found in registry\"))?;\n            let runtime_id = registry\n                .runtime_id_for(principal, id)\n                .ok_or_else(|| anyhow::anyhow!(\"capsule '{id}' not found in registry\"))?;\n            if expected_runtime.is_some_and(|expected| expected != &runtime_id) {\n                return Ok(RestartOutcome::Superseded);\n            }\n            let source_dir = capsule\n                .source_dir()\n                .map(std::path::Path::to_path_buf)\n                .ok_or_else(|| anyhow::anyhow!(\"capsule '{id}' has no source directory\"))?;\n            (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            }","sourceCodeStart":2114,"sourceCodeEnd":2150,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L2114-L2150","documentation":"Thrown during capsule restart when the registered capsule's `source_dir()` is `None` (lib.rs:2132). The restart procedure rebuilds the capsule from its on-disk source, so a registry entry without a source directory cannot be rebuilt and the operation is refused. This indicates a capsule that was registered without usable source material.","triggerScenarios":"Calling restart on a capsule whose registry entry has no source directory (lib.rs:2132) — e.g. a capsule loaded from a manifest-only/prepackaged snapshot without source, or source directory removed from disk and pruned from the entry.","commonSituations":"Capsules installed from durable packages rather than source directories being asked to restart; cleanup jobs deleting capsule source while entries remain registered; restarted deployments after volume/mount changes lost the source path.","solutions":["Reinstall or reload the capsule so a valid source directory is associated with its registry entry","Restore the capsule source directory at the expected path, then retry the restart","If the capsule is package-only by design, redeploy the package instead of using the source-based restart path"],"exampleFix":"// before\nkernel.restart_capsule(&principal, &id, None).await?; // Err: no source directory\n// after\nif kernel.capsule_source_dir(&principal, &id).await.is_some() {\n    kernel.restart_capsule(&principal, &id, None).await?;\n} else {\n    kernel.reinstall_capsule(&principal, &id).await?; // restore source\n    kernel.restart_capsule(&principal, &id, None).await?;\n}","handlingStrategy":"validation","validationCode":"let has_source = kernel.capsules()\n    .read().await\n    .get_for(principal, id)\n    .and_then(|c| c.source_dir().map(|p| p.exists()))\n    .unwrap_or(false);\nif !has_source { /* reinstall before restart */ }","typeGuard":"fn has_source_dir(capsule: &CapsuleEntry) -> bool {\n    capsule.source_dir().map(|p| p.exists()).unwrap_or(false)\n}","tryCatchPattern":"match kernel.restart_capsule(principal, id, expected).await {\n    Err(e) if e.to_string().contains(\"has no source directory\") => {\n        kernel.reinstall_capsule(principal, id).await?;\n        kernel.restart_capsule(principal, id, expected).await?;\n    },\n    other => other?,\n}","preventionTips":["Never delete capsule source directories while entries remain registered","For package-only capsules use the package redeploy path, not source-based restart","Monitor install volumes/mounts so source paths cannot silently disappear"],"tags":["rust","capsule","filesystem","restart"],"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"}