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

  1. Restore the operator classification so installed scope matches the running scope, then retry.
  2. Unload the running capsule completely, then load the new manifest fresh (which will take the new scope).
  3. 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

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


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)