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.

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)

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.