{"record":{"id":"681669337293b2b7","repo":"astrid-runtime/astrid","slug":"layout-migration-source-changed-while-inventoried","errorCode":null,"errorMessage":"layout migration source changed while inventoried: {}","messagePattern":"layout migration source changed while inventoried: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_records.rs","lineNumber":438,"sourceCode":"                ));\n            }\n            let mut buffer = vec![0_u8; 64 * 1024].into_boxed_slice();\n            let mut file_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 inventory byte count overflow\"))?;\n                file_bytes = file_bytes\n                    .checked_add(read as u64)\n                    .ok_or_else(|| io::Error::other(\"layout inventory file length overflow\"))?;\n                hasher.update(&buffer[..read]);\n            }\n            if file_bytes != metadata.len() {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\n                        \"layout migration source changed while inventoried: {}\",\n                        child_path.display()\n                    ),\n                ));\n            }\n        } else {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"layout migration source contains a special file: {}\",\n                    child_path.display()\n                ),\n            ));\n        }\n    }\n    Ok(())","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_records.rs#L420-L456","documentation":"After hashing a file, the inventory compares the number of bytes actually read against the file length captured at scan time (metadata.len()). A mismatch means the file grew, shrank, or was truncated while it was being read, so the computed blake3 digest would not represent a consistent snapshot. The migration aborts with InvalidData to guarantee the inventory digest is trustworthy.","triggerScenarios":"inventory_directory read a file to EOF and the accumulated file_bytes != metadata.len() — the file's size changed between the initial symlink_metadata and the completion of the streaming read.","commonSituations":"An application still writing logs/state into the legacy directory during migration; a sync client downloading a partially-complete file; a truncated download or disk issue mid-read.","solutions":["Ensure the application using the legacy state directory is fully stopped (check for background daemons) before migrating.","Re-run the migration after the directory is idle; the digest will then match.","Check the reported file for unexpected growth (log files, databases being written) and quiesce its writer.","If corruption is suspected, restore the file from backup and retry."],"exampleFix":"// before: migrating while the app is still running\n$ astrid migrate  # app writes surrealkv mid-hash -> size mismatch\n// after: stop the app first\n$ systemctl --user stop astrid && astrid migrate","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"changed while inventoried\") => {\n        // ensure all writers are stopped, then retry once\n        stop_writers();\n        run_migration()\n    }\n    Err(e) => return Err(e),\n    Ok(v) => v,\n}","preventionTips":["Stop the owning application (daemons included) before migrating; verify with lsof +D <state-dir>.","Migrate at startup/shutdown time, not while the app is live.","Watch for log/state files that grow continuously and relocate them out of the state tree."],"tags":["toctou","filesystem","migration","consistency"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}