astrid-runtime/astrid · error

cannot replace capsule '{id}' for retiring principal '{princ

Error message

cannot replace capsule '{id}' for retiring principal '{principal}'

What it means

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.

Source

Thrown at crates/astrid-kernel/src/lib.rs:2151

            (source_dir, runtime_id)
        };

        // Prepare and prove a route-gated replacement while the current
        // generation remains visible and healthy. A preparation or readiness
        // failure leaves the running view untouched.
        let mut prepared = self
            .prepare_runtime_replacement(id, &source_dir, principal, current_runtime.key().scope())
            .await?;

        let load_guard = self.capsule_load_lock.lock().await;
        if self.capabilities.is_principal_retiring(principal).await {
            drop(load_guard);
            prepared.capsule.retire();
            prepared.capsule.request_cancel();
            if let Err(cleanup) = prepared.capsule.unload().await {
                tracing::warn!(capsule_id = %id, %cleanup, "Failed to unload replacement rejected by principal retirement");
            }
            anyhow::bail!("cannot replace capsule '{id}' for retiring principal '{principal}'");
        }
        let (mut previous, replacement) = {
            let mut registry = self.capsules.write().await;
            if registry.runtime_id_for(principal, id).as_ref() != Some(&current_runtime) {
                drop(registry);
                drop(load_guard);
                prepared.capsule.request_cancel();
                prepared.capsule.unload().await?;
                return Ok(RestartOutcome::Superseded);
            }
            if prepared.system_runtime
                && let Err(error) = registry.validate_system_runtime_replacement(
                    &current_runtime,
                    prepared.capsule.as_ref(),
                    &prepared.runtime_id,
                )
            {
                drop(registry);

View on GitHub (pinned to affd8760f4)

Solutions

  1. Complete or cancel the principal's retirement, then retry the replacement.
  2. Check `capabilities.is_principal_retiring(principal)` before starting a replacement and abort early.
  3. Perform replacements under the default principal when the capsule is not principal-specific.

Example fix

// before
kernel.replace_capsule(id, &principal, source_dir).await?;
// after
if capabilities.is_principal_retiring(&principal).await {
    eprintln!("principal retiring; deferring replacement");
} else {
    kernel.replace_capsule(id, &principal, source_dir).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

if kernel.capabilities().is_principal_retiring(&principal).await {
    return Err(anyhow!("defer replacement: principal retiring"));
}

Try / catch

match kernel.replace_capsule(id, &principal, &source_dir).await {
    Err(e) if e.to_string().contains("retiring principal") => schedule_after_retirement(id, principal),
    other => other,
}

Prevention

When it happens

Trigger: 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).

Common situations: A slow build/prepare during which an operator disables the principal; automated retirement triggered concurrently with a deployment-triggered hot replace.

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/ce3c9ff7ad996161. Report an issue: GitHub.