astrid-runtime/astrid · error
capsule ' ' cannot change runtime scope during live…
Error message
capsule '{id}' cannot change runtime scope during live replacement: running={expected_scope:?}, installed={actual_scope:?} What it means
A live replacement must keep the capsule's runtime scope (Principal-UID-scoped vs SystemResident) identical to the scope of the currently running instance. Changing scope in-flight would break authority isolation, so the kernel compares the installed scope of the replacement against the running scope and rejects any change.
Solutions
- Restore the operator classification so installed scope matches the running scope, then retry.
- Unload the running capsule completely, then load the new manifest fresh (which will take the new scope).
- Restart the daemon after changing residency classification.
Example fix
// before: capsule was added to system_capsules while running principal-scoped kernel.replace_runtime(id, source_dir).await?; // scope change rejected // after kernel.unload_capsule(id, principal).await?; kernel.load_capsule(source_dir, &PrincipalId::default()).await?; // fresh load takes new SystemResident scope
Defensive patterns
Strategy: validation
Validate before calling
let running_scope = current_scope_of(id);
let installed_scope = compute_installed_scope(&source_dir, id)?;
if running_scope != installed_scope {
return Err(anyhow!("residency changed; unload+load required instead of live replacement"));
} Try / catch
match kernel.replace_runtime(id, &source_dir).await {
Err(e) if e.to_string().contains("cannot change runtime scope") => {
kernel.unload_capsule(id, &principal).await?;
kernel.load_capsule(&source_dir, &principal).await
}
other => other,
} Prevention
- Do not mutate system_capsules membership while instances are live.
- Batch residency classification changes with planned restart windows.
- Track running scope alongside installed scope in deployment tooling.
When it happens
Trigger: Live replacement where the running capsule's scope differs from the scope computed for the replacement manifest — e.g., the capsule was added to or removed from `system_capsules` (or residency classification changed) since the running instance started.
Common situations: Operator promotes a capsule to system-resident while an old principal-scoped instance is running; the system_capsules entry was removed but a system-resident instance is still live; manifest residency-affecting fields edited during a hot reload.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cannot replace capsule
- capsule source disappeared while preparing replacement
- live replacement of stdio MCP capsule
- replacement manifest id
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/e9972cb41c497d65.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/lib.rs:2049
let artifact = capsule_instance_hash(&manifest, &runtime_dir);
let system_allowed = self.system_capsules.read().await.contains(id.as_str());
let system_runtime = classify_runtime_residency(&manifest, id, system_allowed)?.is_system();
if system_runtime && !manifest.mcp_servers.is_empty() {
anyhow::bail!(
"system-resident capsule '{id}' cannot host principal-bearing stdio MCP servers"
);
}
// Replacement authority is the authenticated operator classification
// and installed receipt, never the ancestry of `source_dir`.
let actual_scope = if system_runtime {
astrid_capsule::registry::RuntimeScope::SystemResident
} else {
astrid_capsule::registry::RuntimeScope::Principal(
self.principal_directory.uid_for(principal)?,
)
};
if actual_scope != expected_scope {
anyhow::bail!(
"capsule '{id}' cannot change runtime scope during live replacement: \
running={expected_scope:?}, installed={actual_scope:?}"
);
}
if let Some(bound) = bound.as_ref() {
self.confirm_published_materialization(
&runtime_dir,
principal,
&manifest,
&bound.snapshot,
)?;
}
let principal_uid = self.runtime_principal_uid(system_runtime, principal, id)?;
let runtime_id =
self.capsules
.write()
.await
.reserve_runtime_id(id.clone(), artifact, actual_scope)?;View on GitHub (pinned to affd8760f4)