{"record":{"id":"670316068d3685e0","repo":"astrid-runtime/astrid","slug":"invalid-layout-migration-record-error","errorCode":null,"errorMessage":"invalid layout migration record {}: {error}","messagePattern":"invalid layout migration record (.+?): (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_records.rs","lineNumber":119,"sourceCode":"        \"astrid home layout migration transaction v1\",\n        &material_bytes,\n    )))\n}\n\npub(super) fn admit_or_write_canonical<T>(\n    path: &Path,\n    expected: &T,\n    allow_create: bool,\n) -> io::Result<()>\nwhere\n    T: DeserializeOwned + PartialEq + Serialize,\n{\n    let mut expected_bytes = serde_json::to_vec(expected).map_err(io::Error::other)?;\n    expected_bytes.push(b'\\n');\n    match std::fs::read(path) {\n        Ok(actual) => {\n            let parsed: T = serde_json::from_slice(&actual).map_err(|error| {\n                io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\n                        \"invalid layout migration record {}: {error}\",\n                        path.display()\n                    ),\n                )\n            })?;\n            if parsed != *expected || actual != expected_bytes {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\n                        \"layout migration record does not match this transaction: {}\",\n                        path.display()\n                    ),\n                ));\n            }\n            Ok(())\n        },","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_records.rs#L101-L137","documentation":"In admit_or_write_canonical, the existing record file at `path` is read and deserialized into the expected record type T; if serde_json cannot parse it, the function wraps the serde error into InvalidData with this message. It means the on-disk migration record is corrupt or was written by an incompatible format, so the library cannot verify it against the current transaction.","triggerScenarios":"begin_layout_v2_migration or complete_layout_v2 calls admit_or_write_canonical and std::fs::read succeeds but serde_json::from_slice fails on the file contents (truncated write, hand-edited file, foreign JSON, empty file).","commonSituations":"A previous run crashed mid-write leaving a truncated/partial record; a user or tool manually edited the layout record file; a different library version wrote a schema that no longer parses into T.","solutions":["Inspect the file named in the message and fix or remove the corrupt record (it will be rewritten by allow_create paths).","Delete the invalid record file and re-run the migration from a clean state, ensuring the layout directories are otherwise consistent.","Restore the record from backup if the migration must be continued transactionally.","Do not hand-edit layout records; let the library serialize them canonically."],"exampleFix":"// before: hand-edited / truncated record\n{ \"version\": 2, \"status\": \"begin\"   // truncated\n// after: delete and regenerate\nstd::fs::remove_file(record_path)?;\nbegin_layout_v2_migration(&dir)?;","handlingStrategy":"try-catch","validationCode":"fn record_parses<T: serde::de::DeserializeOwned>(path: &Path) -> bool {\n    std::fs::read(path)\n        .map(|b| serde_json::from_slice::<T>(&b).is_ok())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().starts_with(\"invalid layout migration record\") => {\n        // quarantine/delete the corrupt record and re-run the migration\n    },\n    other => other?,\n}","preventionTips":["Never hand-edit layout record files","Ensure writes are atomic (library already stages + renames) so crashes cannot truncate records","Keep the same crate version across begin/complete of a migration","Back up records before manual maintenance"],"tags":["json","corrupt-file","migration","serde"],"backgroundTag":"json-unmarshal-failed","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"}