astrid-runtime/astrid · error

principal-home migration conflict at {}: {detail}

Error message

principal-home migration conflict at {}: {detail}

What it means

conflict_path constructs the 'principal-home migration conflict at {path}: {detail}' error with ErrorKind::AlreadyExists, reported at the level of an on-disk Path. It signals that migration state is inconsistent at that path: e.g. multiple live migration receipts claim the same alias (receipt_uid_for_alias), a destination already exists, or a legacy source that was supposed to be retired is still present during verification. The library fails closed instead of overwriting or duplicating principal data.

Source

Thrown at crates/astrid-kernel/src/principal_home_migration/paths.rs:154

        _ => false,
    }
}

pub(super) fn storage_error(error: &FilesystemError) -> io::Error {
    io::Error::other(format!(
        "authoritative home migration storage error: {error}"
    ))
}

pub(super) fn invalid_source(path: &Path, detail: &str) -> io::Error {
    io::Error::new(
        io::ErrorKind::InvalidData,
        format!("legacy principal-home source {}: {detail}", path.display()),
    )
}

pub(super) fn conflict_path(path: &Path, detail: &str) -> io::Error {
    io::Error::new(
        io::ErrorKind::AlreadyExists,
        format!(
            "principal-home migration conflict at {}: {detail}",
            path.display()
        ),
    )
}

pub(super) fn conflict_fs(path: &FilesystemPath, detail: &str) -> io::Error {
    io::Error::new(
        io::ErrorKind::AlreadyExists,
        format!(
            "principal-home migration conflict at {}: {detail}",
            path.as_str()
        ),
    )
}

View on GitHub (pinned to affd8760f4)

Solutions

  1. Read the {detail} to identify which conflict fired (duplicate receipts, existing destination, non-retired source).
  2. If a previous migration partially completed, inspect the receipt files under the migrations dir and remove duplicates/stale receipts for the alias, then re-run.
  3. If the destination genuinely should be empty, move the existing directory/file at the conflict path out of the way (back it up) before migrating.
  4. Never reuse an alias/PrincipalId with an old live receipt; retire the old receipt first, then retry.

Example fix

// before: two receipts claim the same alias
migrations/receipt-1001.json
migrations/receipt-2042.json   # duplicate for same alias

// after: remove the stale duplicate, keep the authoritative one
$ rm migrations/receipt-2042.json
$ # re-run migrate_legacy_principal_homes
Defensive patterns

Strategy: try-catch

Validate before calling

fn has_conflicting_receipts(migrations_dir: &std::path::Path, alias: &str) -> std::io::Result<bool> {
    let mut count = 0usize;
    for entry in std::fs::read_dir(migrations_dir)? {
        let name = entry?.file_name().to_string_lossy().into_owned();
        if name.starts_with(RECEIPT_PREFIX) && name.ends_with(RECEIPT_SUFFIX) {
            // count receipts whose parsed alias matches; >1 means conflict
            count += 1;
        }
    }
    Ok(count > 1)
}

Try / catch

match migrate_legacy_principal_homes(&home, &fs, &source) {
    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
        // path shown in message; back it up, remove stale receipts/destinations, retry once
        eprintln!("conflict at reported path; resolve state and retry: {e}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: migrate_legacy_principal_homes finding two receipt files for one alias; migrate_one_principal or verify_destinations finding an existing destination at a path; verify_migrated_legacy_principal_sources_retired finding a legacy source still present after migration; retire_one_receipted_source / retire_empty_tree encountering unexpected existing state at the path being retired.

Common situations: A previous partially-completed migration left destinations or receipts behind; the same alias was migrated twice (reused PrincipalId after identity retirement without cleaning receipts); manual copies recreated files under the destination home; interrupted runs left tombstones that make verification see non-retired sources.

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