astrid-runtime/astrid · error

legacy secrets root reappeared after cut-over

Error message

legacy secrets root reappeared after cut-over: {secrets_root}

What it means

Thrown at the end of the legacy migration barrier audit when the legacy secrets root directory itself still exists but is empty. After full cut-over the entire legacy secrets root should be gone; an empty remnant is tolerated only when `allow_empty_cleanup` is set, otherwise the audit returns InvalidData rather than deleting user-visible directories behind the caller's back.

Solutions

  1. Delete the empty legacy secrets root directory (`rmdir <secrets_dir>`; rmdir fails safely if not empty).
  2. Re-run the audit with `allow_empty_cleanup` so the empty root is retired automatically.
  3. Remove any startup/config code that recreates the legacy secrets root directory.
  4. If the root is non-empty, complete the remaining secret imports first; a different error names what remains.
Defensive patterns

Strategy: validation

Validate before calling

let secrets_root = home.secrets_dir();
if secrets_root.exists()
    && std::fs::read_dir(&secrets_root)?.next().is_none()
{
    std::fs::remove_dir(&secrets_root)?; // fails safely if not empty
}
verify_legacy_migration_barrier(&home, allow_empty_cleanup)?;

Type guard

fn is_empty_dir(p: &std::path::Path) -> std::io::Result<bool> {
    Ok(p.is_dir() && std::fs::read_dir(p)?.next().is_none())
}

Try / catch

if let Err(e) = audit {
    if e.kind() == std::io::ErrorKind::InvalidData {
        eprintln!("legacy remnant: {e}; run with allow_empty_cleanup or rmdir the path");
    }
}

Prevention

When it happens

Trigger: Running the migration verification when `home.secrets_dir()` exists with zero entries and `allow_empty_cleanup` is false. Also triggered when leftover principal/host subdirectories were cleaned but the root was left behind by an older tool version.

Common situations: Post-migration audit on a home where an older binary created the secrets root and never removed it; backup/restore tooling recreating the empty directory; scripts that `mkdir -p` the secrets path at startup.

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

Appendix: source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/mod.rs:936

        if allow_empty_cleanup {
            retire_empty_directory(&host_secrets)?;
        } else {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "legacy host secret source reappeared after cut-over: {}",
                    host_secrets.display()
                ),
            ));
        }
    }
    if path_exists(&secrets_root)? {
        let remaining = snapshot_path(&secrets_root)?;
        if remaining.entries == 0 {
            if allow_empty_cleanup {
                retire_empty_directory(&secrets_root)?;
            } else {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!(
                        "legacy secrets root reappeared after cut-over: {}",
                        secrets_root.display()
                    ),
                ));
            }
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests;

View on GitHub (pinned to affd8760f4)