{"record":{"id":"42f6ac0dc5ccb129","repo":"astrid-runtime/astrid","slug":"layout-migration-destination-changed-while-invento","errorCode":null,"errorMessage":"layout migration destination changed while inventoried: {}","messagePattern":"layout migration destination changed while inventoried: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_records.rs","lineNumber":341,"sourceCode":"                path.display()\n            ),\n        ));\n    }\n    let mut hasher = blake3::Hasher::new_derive_key(\"astrid layout destination inventory v1\");\n    let mut buffer = vec![0_u8; 64 * 1024].into_boxed_slice();\n    let mut bytes = 0_u64;\n    loop {\n        let read = file.read(&mut buffer)?;\n        if read == 0 {\n            break;\n        }\n        bytes = bytes\n            .checked_add(read as u64)\n            .ok_or_else(|| io::Error::other(\"layout destination length overflow\"))?;\n        hasher.update(&buffer[..read]);\n    }\n    if bytes != metadata.len() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\n                \"layout migration destination changed while inventoried: {}\",\n                path.display()\n            ),\n        ));\n    }\n    Ok(LayoutTreeIdentityV1 {\n        path_encoding: \"os-str-encoded-bytes-v1\".to_owned(),\n        physical_path_hex: physical_path_hex(path)?,\n        inventory_algorithm: \"blake3-derive-key-v1\".to_owned(),\n        inventory_digest: hasher.finalize().to_hex().to_string(),\n        entries: 1,\n        bytes,\n    })\n}\n\nfn inventory_directory(","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_records.rs#L323-L359","documentation":"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.)","triggerScenarios":"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().","commonSituations":"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.","solutions":["Quiesce all writers to the destination volume, then re-run the migration/inventory.","Retry begin_layout_v2_migration when the system is idle so the file is stable during hashing.","Check for scheduled jobs (backup, sync, logrotate) that touch the volume and exclude the destination path."],"exampleFix":"// before: writers active during inventory\n// after: ensure exclusive access, then migrate\nflock /data/astrid-volume  # or stop the service holding the volume open, re-run migration","handlingStrategy":"retry","validationCode":"let before = std::fs::metadata(&dest_path)?.len();\nstd::thread::sleep(std::time::Duration::from_secs(1));\nif std::fs::metadata(&dest_path)?.len() != before {\n    return Err(\"destination is being written; quiesce writers first\");\n}","typeGuard":null,"tryCatchPattern":"match begin_layout_v2_migration(...) {\n    Err(e) if e.to_string().contains(\"changed while inventoried\") => {\n        quiesce_writers(&dest_path);\n        retry_with_backoff(begin_layout_v2_migration, 3);\n    },\n    other => other,\n}","preventionTips":["Quiesce all writers (stop Astrid, sync clients, backups) before inventory.","Schedule migrations during idle windows.","Exclude the volume path from logrotate/snapshot jobs during migration."],"tags":["filesystem","race-condition","migration","io"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}