{"record":{"id":"87696dd103779f35","repo":"astrid-runtime/astrid","slug":"layout-migration-destination-changed-type","errorCode":null,"errorMessage":"layout migration destination changed type: {}","messagePattern":"layout migration destination changed type: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_records.rs","lineNumber":319,"sourceCode":"    crate::platform_fs::verify_no_redirects(path)?;\n    let mut options = OpenOptions::new();\n    options.read(true);\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::OpenOptionsExt as _;\n\n        options.custom_flags(nix::libc::O_NOFOLLOW | nix::libc::O_NONBLOCK);\n    }\n    #[cfg(windows)]\n    {\n        use std::os::windows::fs::OpenOptionsExt as _;\n        use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;\n\n        options.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);\n    }\n    let mut file = options.open(path)?;\n    if !file.metadata()?.is_file() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\n                \"layout migration destination changed type: {}\",\n                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\"))?;","sourceCodeStart":301,"sourceCodeEnd":337,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_records.rs#L301-L337","documentation":"After opening the destination with reparse-point-safe flags (FILE_FLAG_OPEN_REPARSE_POINT on Windows), inventory_regular_file re-checks file.metadata() and requires it to still be a regular file. If the opened handle's metadata shows the entry changed type since the initial symlink_metadata check (TOCTOU race) or never was a regular file, this InvalidData error naming the path is raised.","triggerScenarios":"Called from begin_layout_v2_migration / LayoutMigrationReceiptV1 when the destination entry is swapped (file -> directory, or a reparse point is resolved) between the initial symlink_metadata check and the opened file's metadata() call.","commonSituations":"A concurrent process (backup agent, sync tool, migration script) replaced or retyped the destination during inventory; Windows junction/symlink created over the path mid-migration; antivirus quarantine replaced the file.","solutions":["Stop competing processes that touch the destination path, then re-run the migration.","Inspect the path (`stat`, `fsutil reparsepoint query`) and restore it to a plain regular file.","Re-run begin_layout_v2_migration on a stable system so the two checks agree."],"exampleFix":"// before: sync daemon rewrites destination mid-inventory\n// after: pause competing writers, then migrate\nsystemctl --user stop backup-sync  # then re-run begin_layout_v2_migration","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match begin_layout_v2_migration(...) {\n    Err(e) if e.to_string().contains(\"changed type\") => {\n        quiesce_writers(&dest_path);\n        retry_with_backoff(begin_layout_v2_migration, 3);\n    },\n    other => other,\n}","preventionTips":["Stop backup/sync/AV agents that rewrite the volume before migrating.","Run migration with exclusive access to the destination path.","Re-run migration after any suspected concurrent modification — the check is a TOCTOU guard by design."],"tags":["filesystem","race-condition","migration","io"],"backgroundTag":"invalid-state-transition","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"}