astrid-runtime/astrid · error

legacy capsule {id} metadata changed before retirement

Error message

legacy capsule {id} metadata changed before retirement

What it means

As a final pre-retirement check, migration re-reads meta.json and compares it byte-for-byte to the metadata that was published durably. A difference means the legacy metadata changed during migration, so the legacy directory is not retired and the error is raised to protect the mutated data.

Source

Thrown at crates/astrid-capsule-install/src/storage/migration.rs:197

        };
        registry.install(&owner, id, &package, expectation)?;
        let readback = registry
            .get_snapshot(&owner, id)?
            .ok_or_else(|| anyhow::anyhow!("durable capsule {id} disappeared after publish"))?;
        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(),

View on GitHub (pinned to affd8760f4)

Solutions

  1. Stop any process that could write meta.json, then re-run migration
  2. Serialize migrations so only one runs at a time
  3. Restore meta.json to the version matching the published package, or re-migrate from scratch

Example fix

// before
migrate while capsule service updates meta.json
// after
systemctl --user stop my-capsule; migrate_all_native_capsules(store)
Defensive patterns

Strategy: validation

Validate before calling

// verify meta.json stability before migrating
let before = std::fs::read(capsule_dir.join("meta.json")).map_err(|e| e.to_string())?;
std::thread::sleep(Duration::from_secs(1));
if std::fs::read(capsule_dir.join("meta.json")).map_err(|e| e.to_string())? != before {
    return Err("meta.json is being rewritten".into());
}

Try / catch

if let Err(e) = migrate_native_capsules(home, store) {
    if e.to_string().contains("metadata changed before retirement") {
        eprintln!("meta.json changed mid-migration; stop the writer and retry");
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: meta.json was rewritten between its initial read and the final retirement check — e.g. the capsule process updated its own metadata, an installer touched it, or a sync tool restored a different copy.

Common situations: Running capsule writing version/state updates to meta.json mid-migration; backup/restore tools rewriting metadata; concurrent second migration attempt.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/b23d384e43340f6d. Report an issue: GitHub.