astrid-runtime/astrid · error · io::Error

layout migration destination changed while inventoried: {}

Error message

layout migration destination changed while inventoried: {}

What it means

While hashing the destination file, inventory_regular_file accumulates the bytes actually read and, after EOF, compares the total with metadata.len() captured at open time. If they differ — the file grew, shrank, or was rewritten while being inventoried — this InvalidData error is raised instead of issuing a receipt for an inconsistent fingerprint. (An arithmetic overflow of the running total raises io::Error::other separately.)

Source

Thrown at crates/astrid-core/src/dirs_layout_records.rs:341

                path.display()
            ),
        ));
    }
    let mut hasher = blake3::Hasher::new_derive_key("astrid layout destination inventory v1");
    let mut buffer = vec![0_u8; 64 * 1024].into_boxed_slice();
    let mut bytes = 0_u64;
    loop {
        let read = file.read(&mut buffer)?;
        if read == 0 {
            break;
        }
        bytes = bytes
            .checked_add(read as u64)
            .ok_or_else(|| io::Error::other("layout destination length overflow"))?;
        hasher.update(&buffer[..read]);
    }
    if bytes != metadata.len() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "layout migration destination changed while inventoried: {}",
                path.display()
            ),
        ));
    }
    Ok(LayoutTreeIdentityV1 {
        path_encoding: "os-str-encoded-bytes-v1".to_owned(),
        physical_path_hex: physical_path_hex(path)?,
        inventory_algorithm: "blake3-derive-key-v1".to_owned(),
        inventory_digest: hasher.finalize().to_hex().to_string(),
        entries: 1,
        bytes,
    })
}

fn inventory_directory(

View on GitHub (pinned to affd8760f4)

Solutions

  1. Quiesce all writers to the destination volume, then re-run the migration/inventory.
  2. Retry begin_layout_v2_migration when the system is idle so the file is stable during hashing.
  3. Check for scheduled jobs (backup, sync, logrotate) that touch the volume and exclude the destination path.

Example fix

// before: writers active during inventory
// after: ensure exclusive access, then migrate
flock /data/astrid-volume  # or stop the service holding the volume open, re-run migration
Defensive patterns

Strategy: retry

Validate before calling

let before = std::fs::metadata(&dest_path)?.len();
std::thread::sleep(std::time::Duration::from_secs(1));
if std::fs::metadata(&dest_path)?.len() != before {
    return Err("destination is being written; quiesce writers first");
}

Try / catch

match begin_layout_v2_migration(...) {
    Err(e) if e.to_string().contains("changed while inventoried") => {
        quiesce_writers(&dest_path);
        retry_with_backoff(begin_layout_v2_migration, 3);
    },
    other => other,
}

Prevention

When it happens

Trigger: Called from begin_layout_v2_migration / LayoutMigrationReceiptV1 when the destination file's content changes between the metadata() snapshot and the completion of the hashing read loop, so bytes != metadata.len().

Common situations: Astrid itself or another writer appending to/rewriting the volume during migration; log rotation or snapshot tooling truncating the file mid-read; a sync client downloading changes to the destination concurrently.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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