astrid-runtime/astrid · error

migration ledger contains duplicate component: {}

Error message

migration ledger contains duplicate component: {}

What it means

`validate_ledger_shape` inserts each component name into a `BTreeSet` and fails if a name is inserted twice. This error is thrown when the ledger's `components` array contains two or more entries with the identical component name, which would make the migration state ambiguous (which entry's proof is authoritative?).

Source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs:614

}

pub(super) fn canonical_json<T: Serialize>(value: &T) -> io::Result<Vec<u8>> {
    let mut bytes = serde_json::to_vec(value).map_err(io::Error::other)?;
    bytes.push(b'\n');
    Ok(bytes)
}

#[allow(
    clippy::too_many_lines,
    reason = "all ledger invariants are checked before admission"
)]
pub(super) fn validate_ledger_shape(ledger: &MigrationLedger) -> io::Result<()> {
    let mut names = std::collections::BTreeSet::new();
    let mut previous = None;
    for component in &ledger.components {
        validate_component_name(&component.name)?;
        if !names.insert(component.name.clone()) {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "migration ledger contains duplicate component: {}",
                    component.name
                ),
            ));
        }
        if previous
            .as_ref()
            .is_some_and(|previous: &String| previous >= &component.name)
        {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "migration ledger components are not canonically sorted",
            ));
        }
        previous = Some(component.name.clone());
        if component.source.present && component.source.digest == "absent" {

View on GitHub (pinned to affd8760f4)

Solutions

  1. De-duplicate the components array so each component name appears exactly once, keeping the entry whose `destination_proof` matches the on-disk receipts.
  2. Prefer regenerating the ledger via the library (`write_ledger`) over hand-de-duplication, so proofs stay consistent with the filesystem.
  3. If the duplication came from a Git merge, redo the merge resolving the ledger file by taking one side's component entry, not concatenating both.
  4. After fixing, re-run the migration resume; the validation will confirm uniqueness.

Example fix

// before
"components": [
  {"name": "principal:01H8X:home", "source": {...}, "destination_proof": "A"},
  {"name": "principal:01H8X:home", "source": {...}, "destination_proof": "B"}
]
// after
"components": [
  {"name": "principal:01H8X:home", "source": {...}, "destination_proof": "A"}
]
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: component names are unique before writing/validating
fn no_duplicate_components(ledger: &MigrationLedger) -> bool {
    let mut seen = std::collections::BTreeSet::new();
    ledger.components.iter().all(|c| seen.insert(c.name.clone()))
}

Prevention

When it happens

Trigger: Calling `validate_ledger_shape` (via `write_ledger` or the decode/validate paths like `reject_incomplete_layout_v2`, `retire_post_barrier_sources`, `resume_existing_layout`) on a ledger JSON where the same `name` appears in multiple component entries — typically from a botched manual merge or a duplicated array element.

Common situations: Merging two branches that both appended the same component to the ledger; a copy-paste duplication when hand-editing; a buggy external tool appending components without de-duplicating.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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