astrid-runtime/astrid · error · io::Error
layout migration record does not match this transaction: {}
Error message
layout migration record does not match this transaction: {} What it means
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.
Source
Thrown at crates/astrid-core/src/dirs_layout_records.rs:128
) -> io::Result<()>
where
T: DeserializeOwned + PartialEq + Serialize,
{
let mut expected_bytes = serde_json::to_vec(expected).map_err(io::Error::other)?;
expected_bytes.push(b'\n');
match std::fs::read(path) {
Ok(actual) => {
let parsed: T = serde_json::from_slice(&actual).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"invalid layout migration record {}: {error}",
path.display()
),
)
})?;
if parsed != *expected || actual != expected_bytes {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"layout migration record does not match this transaction: {}",
path.display()
),
));
}
Ok(())
},
Err(error) if error.kind() == io::ErrorKind::NotFound && allow_create => {
super::atomic_write(path, &expected_bytes)
},
Err(error) if error.kind() == io::ErrorKind::NotFound => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("layout migration intent is missing: {}", path.display()),
)),
Err(error) => Err(error),
}View on GitHub (pinned to affd8760f4)
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.
Example fix
// before: pretty-printed record breaks canonical-byte check
{ "version": 2 }
// after: let the library write it
std::fs::remove_file(record_path)?;
begin_layout_v2_migration(&dir)?; // rewrites canonical bytes Defensive patterns
Strategy: validation
Validate before calling
// verify the on-disk record matches this transaction before completing
let bytes = std::fs::read(record_path)?;
let expected = serde_json::to_vec(&expected_record)?;
if bytes != expected { return Err("record belongs to another transaction"); } Try / catch
match result {
Err(e) if e.to_string().contains("does not match this transaction") => {
// inspect the file, finish/roll back the other transaction, then retry
},
other => other?,
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- invalid layout migration record {}: {error}
- layout migration record is not canonical: {}
- legacy source changed before retirement: {}
- legacy component source reappeared during ordinary retiremen
- decode migration ledger {}: {error}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/281e093b343bdb0c.
Report an issue: GitHub.