{"record":{"id":"281e093b343bdb0c","repo":"astrid-runtime/astrid","slug":"layout-migration-record-does-not-match-this-transa","errorCode":null,"errorMessage":"layout migration record does not match this transaction: {}","messagePattern":"layout migration record does not match this transaction: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout_records.rs","lineNumber":128,"sourceCode":") -> 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        },\n        Err(error) if error.kind() == io::ErrorKind::NotFound && allow_create => {\n            super::atomic_write(path, &expected_bytes)\n        },\n        Err(error) if error.kind() == io::ErrorKind::NotFound => Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"layout migration intent is missing: {}\", path.display()),\n        )),\n        Err(error) => Err(error),\n    }","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout_records.rs#L110-L146","documentation":"After successfully parsing the record, admit_or_write_canonical compares it to the expected transaction record, both by value and byte-for-byte (including the trailing newline produced by canonical serialization). Any mismatch means the file on disk belongs to a different migration/transaction than the one being executed, so the library aborts with InvalidData rather than mixing transactions.","triggerScenarios":"begin_layout_v2_migration or complete_layout_v2 finds an existing record whose parsed value differs from `expected`, or whose raw bytes are not the canonical serialization of the expected record (extra whitespace, missing trailing newline, different fields/timestamps).","commonSituations":"Two concurrent or interleaved migrations started with different parameters writing the same record path; a stale record left from an aborted earlier migration attempt; a tool reformatted (pretty-printed) the JSON file, breaking byte-canonicality.","solutions":["Inspect the record file at the reported path to see which transaction it belongs to; complete or roll back that transaction first.","Remove the stale/foreign record and re-run the current migration cleanly.","Ensure migrations are not run concurrently against the same layout directory (add locking in the caller).","Never reformat or regenerate the JSON manually — records must be byte-canonical as the library writes them."],"exampleFix":"// before: pretty-printed record breaks canonical-byte check\n{ \"version\": 2 }\n// after: let the library write it\nstd::fs::remove_file(record_path)?;\nbegin_layout_v2_migration(&dir)?; // rewrites canonical bytes","handlingStrategy":"validation","validationCode":"// verify the on-disk record matches this transaction before completing\nlet bytes = std::fs::read(record_path)?;\nlet expected = serde_json::to_vec(&expected_record)?;\nif bytes != expected { return Err(\"record belongs to another transaction\"); }","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"does not match this transaction\") => {\n        // inspect the file, finish/roll back the other transaction, then retry\n    },\n    other => other?,\n}","preventionTips":["Serialize migrations with an exclusive lock per layout directory","Never run two migrations with different parameters against the same directory","Do not reformat record JSON (must stay byte-canonical)","Clean up stale records from aborted migrations before starting a new one"],"tags":["migration","atomicity","concurrency","json"],"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"}