astrid-runtime/astrid · error

legacy distro conflict at {path}: {detail}

Error message

legacy distro conflict at {path}: {detail}

What it means

This io::Error with ErrorKind::AlreadyExists signals a conflict during legacy distro migration: something the migration wants to create or retire already exists in an incompatible state at {path}. The {detail} describes the colliding artifact (e.g. an existing lock or destination).

Source

Thrown at crates/astrid-kernel/src/principal_distro_migration.rs:569

}

#[cfg(not(unix))]
// Windows publication is already write-through; keep the shared fallible
// signature so migration call sites remain platform-independent.
#[allow(clippy::unnecessary_wraps)]
fn sync_parent(_path: &Path) -> io::Result<()> {
    Ok(())
}

fn invalid(path: &Path, detail: &str) -> io::Error {
    io::Error::new(
        io::ErrorKind::InvalidData,
        format!("legacy distro {}: {detail}", path.display()),
    )
}

fn conflict(path: &Path, detail: &str) -> io::Error {
    io::Error::new(
        io::ErrorKind::AlreadyExists,
        format!("legacy distro conflict at {}: {detail}", path.display()),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    fn lock_text() -> &'static str {
        "schema-version = 1\n\n[distro]\nid = \"example\"\nversion = \"1.0.0\"\nresolved-at = \"2026-01-01T00:00:00Z\"\n"
    }

    #[test]
    fn source_snapshot_counts_only_the_private_lock() {
        let directory = tempfile::tempdir().unwrap();
        let home = AstridHome::from_path(directory.path().join("astrid"));
        let alias = PrincipalId::new("alice").unwrap();

View on GitHub (pinned to affd8760f4)

Solutions

  1. Check whether a previous migration run partially completed and clean up stale locks at {path}
  2. Ensure only one migration process runs at a time
  3. Remove or rename the conflicting artifact once its state is understood
  4. Re-run the migration, which should now proceed idempotently

Example fix

// before: hard failure on existing lock
retire_legacy_distro_init_locks(path)?;
// after: tolerate already-retired locks
match retire_legacy_distro_init_locks(path) {
    Ok(()) => {},
    Err(e) if e.kind() == io::ErrorKind::AlreadyExists => log::info!("locks already retired"),
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

if init_lock_path.exists() { cleanup_stale_lock(init_lock_path)?; }

Type guard

fn lock_is_stale(p: &Path, max_age: Duration) -> bool {
    p.metadata().map(|m| m.modified().map(|t| t.elapsed().unwrap_or_default() > max_age).unwrap_or(true)).unwrap_or(false)
}

Try / catch

match migrate_one(path) {
    Err(e) if e.kind() == io::ErrorKind::AlreadyExists => cleanup_and_retry(path)?,
    Err(e) => return Err(e),
    Ok(()) => {},
}

Prevention

When it happens

Trigger: Calling conflict(path, detail) from retire_legacy_distro_init_locks, migrate_one, retire_source, read_digest, or read_bounded when an init lock, destination entry, or source already exists or is unexpectedly present.

Common situations: Re-running a partially completed migration, concurrent migration processes racing on the same distro, stale lock files left by a crashed prior run.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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