astrid-runtime/astrid · error

legacy secret source reappeared after cut-over

Error message

legacy secret source reappeared after cut-over: {}

What it means

When cut-over is complete, legacy secret source directories must stay empty. If a directory has content again and empty-dir cleanup is not allowed (`allow_empty_cleanup == false`), the library raises `InvalidData` because a legacy secret source reappeared after migration, meaning aliases would shadow live data.

Solutions

  1. Find and stop whatever is writing to the legacy path (process, cron, sync client), remove the recreated files, and re-run migration.
  2. Update applications to use the new secret location instead of the legacy root.
  3. Run migration with cleanup allowed only after confirming nothing recreates legacy content.

Example fix

# before: cron writes to old path
0 * * * * write-secret legacy-secrets/api/token.txt
# after
0 * * * * write-secret .astrid/secrets/api/token.txt
Defensive patterns

Strategy: try-catch

Validate before calling

fn legacy_quiet(legacy_root: &std::path::Path) -> bool {
    std::fs::read_dir(legacy_root).map(|rd| rd.filter_map(Result::ok)
        .all(|e| std::fs::read_dir(e.path()).map(|mut d| d.next().is_none()).unwrap_or(true)))
    .unwrap_or(true)
}

Try / catch

match ensure_legacy_secret_aliases(root, ...) {
    Err(e) if e.to_string().contains("reappeared after cut-over") => {
        eprintln!("something rewrote legacy secrets; stop writers, clean, re-run");
        return Err(e.into());
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `ensure_legacy_secret_aliases` after cut-over when a previously retired legacy directory contains files again and the `allow_empty_cleanup` path was not taken (i.e. the error branch runs instead of `retire_empty_directory`).

Common situations: An application or cron job rewrote secrets into the old legacy location after migration; a restore/sync tool recreated old files; the migration was run twice with an app still writing legacy paths.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:121

    for entry in entries {
        if entry.file_name() == "__host__" {
            continue;
        }
        let path = entry.path();
        let snapshot = snapshot_path(&path)?;
        if snapshot.entries != 0 {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "legacy secret source remains after migration: {}",
                    path.display()
                ),
            ));
        }
        if allow_empty_cleanup {
            retire_empty_directory(&path)?;
        } else {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "legacy secret source reappeared after cut-over: {}",
                    path.display()
                ),
            ));
        }
    }
    Ok(())
}

/// Collect capsule directories below a workspace portal without following
/// redirects.  The migration barrier uses this inventory when checking that
/// no legacy authority receipts remain attached to a workspace capsule.
pub(super) fn collect_workspace_targets(root: &Path) -> io::Result<Vec<PathBuf>> {
    const MAX_WORKSPACE_TARGETS: usize = 4096;
    let metadata = match fs::symlink_metadata(root) {
        Ok(metadata) => metadata,

View on GitHub (pinned to affd8760f4)