astrid-runtime/astrid · error

legacy secret source reappeared after completed migration

Error message

legacy secret source reappeared after completed migration: {path}

What it means

Raised by `ensure_legacy_secret_deletion_allowed`: the legacy secret directory for a principal still exists (`secrets_dir/<principal>`), and the migration ledger records that this principal's secret source was migrated away (`source.present == false`). After a completed migration the legacy source must not exist, so its reappearance is blocked with WouldBlock to prevent operating on stale, untracked secrets.

Solutions

  1. Inspect and remove the reappeared legacy directory `<secrets_dir>/<principal>` after confirming no needed data lives there (its contents were migrated).
  2. Reconcile the ledger: if the migration actually did not complete, update/redo the import so `source.present` reflects reality.
  3. Check for old-version processes or backup jobs that recreate the legacy path and stop them.
  4. Re-run the deletion/verification flow after cleanup.
Defensive patterns

Strategy: try-catch

Validate before calling

let legacy = home.secrets_dir().join(principal.as_str());
if legacy.symlink_metadata().is_ok() {
    eprintln!("legacy secret dir still exists for {principal}; reconcile ledger before deletion");
}

Type guard

fn legacy_secret_path_exists(home: &AstridHome, principal: &PrincipalId) -> bool {
    home.secrets_dir().join(principal.as_str()).symlink_metadata().is_ok()
}

Try / catch

match ensure_legacy_secret_deletion_allowed(&home, &principal, uid) {
    Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
        eprintln!("legacy secret source reappeared; inspect {} before retry", e);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `ensure_legacy_secret_deletion_allowed` when `fs::symlink_metadata(secrets_dir/<principal>)` succeeds AND `legacy_secret_source_must_be_absent` returns true — i.e. the ledger says migration completed for `principal:<uid>:secrets` but the legacy directory is back on disk.

Common situations: Restoring the legacy secrets directory from a backup after migration; an old kernel version recreating per-principal secret dirs; a copy operation that resurrected the legacy path during maintenance.

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

Appendix: source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/secret.rs:49

    let ledger: MigrationLedger = decode_canonical(&bytes, &path)?;
    let name = format!("principal:{uid}:secrets");
    let component = ledger
        .components
        .iter()
        .find(|component| component.name == name);
    Ok(component.is_some_and(|component| !component.source.present))
}

pub(crate) fn ensure_legacy_secret_deletion_allowed(
    home: &AstridHome,
    principal: &PrincipalId,
    uid: PrincipalUid,
) -> io::Result<()> {
    let path = home.secrets_dir().join(principal.as_str());
    match fs::symlink_metadata(&path) {
        Ok(_) => {
            if legacy_secret_source_must_be_absent(home, uid)? {
                return Err(io::Error::new(
                    io::ErrorKind::WouldBlock,
                    format!(
                        "legacy secret source reappeared after completed migration: {}",
                        path.display()
                    ),
                ));
            }
        },
        Err(error) if error.kind() == io::ErrorKind::NotFound => (),
        Err(error) => return Err(error),
    }
    Ok(())
}

#[cfg(test)]
pub(crate) fn record_absent_legacy_secret_for_test(
    home: &AstridHome,
    uid: PrincipalUid,

View on GitHub (pinned to affd8760f4)