{"record":{"id":"eabe832df8dc30d3","repo":"astrid-runtime/astrid","slug":"legacy-source-changed-type","errorCode":null,"errorMessage":"legacy source changed type: {}","messagePattern":"legacy source changed type: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":817,"sourceCode":"        validate_private_entry(path, metadata)\n    }\n}\n\nfn read_regular_file(\n    path: &Path,\n    hasher: &mut blake3::Hasher,\n    identity: &mut SourceInventory,\n) -> io::Result<()> {\n    let mut options = OpenOptions::new();\n    options.read(true);\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::OpenOptionsExt as _;\n        options.custom_flags(nix::libc::O_NOFOLLOW | nix::libc::O_CLOEXEC | nix::libc::O_NONBLOCK);\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!(\"legacy source changed type: {}\", path.display()),\n        ));\n    }\n    let mut buffer = vec![0_u8; 64 * 1024].into_boxed_slice();\n    loop {\n        let read = file.read(&mut buffer)?;\n        if read == 0 {\n            break;\n        }\n        identity.bytes = identity\n            .bytes\n            .checked_add(read as u64)\n            .ok_or_else(|| io::Error::other(\"legacy source byte limit exceeded\"))?;\n        if identity.bytes.get() > MAX_BYTES {\n            return Err(io::Error::other(\"legacy source byte limit exceeded\"));\n        }\n        hasher.update(&buffer[..read]);","sourceCodeStart":799,"sourceCodeEnd":835,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L799-L835","documentation":"This error is thrown by `read_regular_file` when a legacy source path that was expected to be a regular file no longer is. After opening the file with O_NOFOLLOW (so symlinks fail at open) and O_NONBLOCK, the code calls `file.metadata()` and rejects anything whose type is not a plain file. The library throws this to guarantee the legacy source being migrated cannot silently change identity between path validation and read.","triggerScenarios":"Calling read_regular_file (via snapshot_path_with_access or snapshot_dir) on a path that is a FIFO, device, socket, or changed type after validation was performed.","commonSituations":"A special file (e.g. /dev entry or FIFO) is present in the legacy home directory; a file was replaced by a symlink or device node mid-migration; a security tool or another process mutated the legacy layout concurrently.","solutions":["Inspect the path reported in the message with `ls -la` and `stat` to see what it actually is.","Remove or relocate the special entry so the legacy source contains only regular files.","Re-run the migration; ensure no external process rewrites the legacy directory during migration."],"exampleFix":"// before: path is a FIFO left by another tool\n$ mkfifo ~/.legacy-app/state\n// after: remove the special entry so migration sees a regular file\n$ rm ~/.legacy-app/state","handlingStrategy":"validation","validationCode":"let md = std::fs::symlink_metadata(path)?;\nif !md.is_file() {\n    return Err(anyhow!(\"skip migration: {} is not a regular file\", path.display()));\n}","typeGuard":"fn is_regular_file(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_file()).unwrap_or(false)\n}","tryCatchPattern":"match astrid_kernel::read_regular_file(path) {\n    Err(e) if e.to_string().contains(\"legacy source changed type\") => {\n        log::warn!(\"skipping special entry {}\", path.display());\n    }\n    Err(e) => return Err(e.into()),\n    Ok(data) => { /* proceed */ }\n}","preventionTips":["Scan legacy directories for non-regular entries with `find <dir> ! -type f ! -type d` before migrating.","Keep daemons and sync tools from creating sockets/FIFOs inside legacy paths during migration.","Open sources with O_NOFOLLOW semantics yourself to fail fast on symlink swaps."],"tags":["io","filesystem","security","migration"],"backgroundTag":"incompatible-source-type","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}