astrid-runtime/astrid · error
legacy capsule {id} authority changed before retirement
Error message
legacy capsule {id} authority changed before retirement What it means
This error is raised during legacy capsule migration when the installed capsule at the migration target no longer matches the expected state at retirement time. Specifically, the bytes recorded as the installed authority for the capsule no longer equal the authority bytes from the legacy source being migrated. Migration is a one-shot, verifiable copy: if the target's authority was modified between an earlier partial migration and this pass, the code refuses to retire the legacy source rather than destroy a divergent installation.
Source
Thrown at crates/astrid-capsule-install/src/storage/migration.rs:202
if readback.package() != &package {
bail!("durable capsule {id} failed byte-for-byte readback");
}
read_verified_durable_package_for_owner(store, &owner, id)?.ok_or_else(|| {
anyhow::anyhow!("durable capsule {id} failed authoritative verification")
})?;
astrid_core::platform_fs::verify_no_redirects(&target)
.with_context(|| format!("verify legacy capsule {id} before retirement"))?;
let final_archive = canonical_legacy_archive(home, &target, &meta, &manifest)?;
if final_archive != package.archive {
bail!("legacy capsule {id} changed before retirement");
}
if fs::read(target.join("meta.json"))? != package.metadata {
bail!("legacy capsule {id} metadata changed before retirement");
}
if read_installed_authority_bytes(home, &target)?.as_deref()
!= Some(source_authority_bytes.as_slice())
{
bail!("legacy capsule {id} authority changed before retirement");
}
astrid_core::platform_fs::verify_no_redirects(&target)
.with_context(|| format!("verify legacy capsule {id} retirement boundary"))?;
astrid_core::dirs::retire_legacy_source_tree(&target)
.with_context(|| format!("retire migrated legacy capsule {id}"))?;
retire_legacy_authority_receipt(home, &target, &source_authority_bytes)
.with_context(|| format!("retire migrated legacy capsule {id} authority"))?;
report
.retired_authorities
.push(LegacyCapsuleAuthorityReceipt {
uid,
capsule_id: id.to_owned(),
authority_digest: blake3::hash(&package.authority).to_hex().to_string(),
});
}
Ok(report)
}
View on GitHub (pinned to affd8760f4)
Solutions
- Inspect the target capsule's installed authority receipt and compare it with the legacy source authority; determine which is current.
- If the target is the newer, correct install, remove it (or its receipt) so migration can redo the copy cleanly, or finish retirement manually with retire_legacy_source_tree and retire_legacy_authority_receipt.
- If the legacy source is newer, re-copy it over the target so authority bytes match, then re-run migration.
- Avoid running concurrent migrations; ensure only one astrid process migrates at a time.
Example fix
// before (target diverged after partial migration)
// migration bails: authority bytes differ
// after: reset diverged target so migration can complete atomically
let target = home.capsules().join("my-capsule");
std::fs::remove_dir_all(&target)?; // only if the legacy source is authoritative
let report = migrate_native_capsules(&home, &directory)?; Defensive patterns
Strategy: validation
Validate before calling
let target = home.capsules().join(alias);
if target.exists() {
let installed = read_installed_authority_bytes(home, &target)?;
if installed.as_deref() != Some(source_authority_bytes.as_slice()) {
eprintln!("target diverged; reset or reconcile before migrating");
}
} Type guard
fn authority_matches(installed: Option<&[u8]>, source: &[u8]) -> bool {
installed == Some(source)
} Try / catch
match migrate_native_capsules(&home, &directory) {
Ok(report) => println!("migrated: {:?}", report),
Err(e) if e.to_string().contains("authority changed before retirement") => {
eprintln!("target capsule diverged; reconcile and re-run");
}
Err(e) => return Err(e),
} Prevention
- Never modify or upgrade a capsule in the target location between migration attempts.
- Run migration single-threaded; avoid concurrent astrid processes during migration.
- Treat install receipts under the capsule home as generated files — never hand-edit them.
- After a failed migration, re-run from a known state (delete the partial target if the legacy source is authoritative).
When it happens
Trigger: Calling migrate_native_capsules, migrate_all_native_capsules_with_report, or migrate_legacy_layout when the target capsule directory's installed authority receipt (read via read_installed_authority_bytes) differs from the source package's authority bytes — e.g. the capsule was re-installed or upgraded after a previous interrupted migration, or the receipt file was edited/deleted.
Common situations: A first migration attempt partially completed (copied files, wrote a receipt) but crashed before retiring the legacy tree; the user then upgraded the capsule in the new location; then re-ran migration. Also occurs when users hand-edit install receipts or when two migration processes race.
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
- legacy capsule component path is not relative
- legacy capsule metadata has no WASM hash
- legacy capsule WIT path is not relative: {}
- legacy capsule directory {id} does not match manifest id {}
- legacy capsule metadata version differs for {id}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/ac26903224346707.
Report an issue: GitHub.