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

  1. Inspect the record file at the reported path to see which transaction it belongs to; complete or roll back that transaction first.
  2. Remove the stale/foreign record and re-run the current migration cleanly.
  3. Ensure migrations are not run concurrently against the same layout directory (add locking in the caller).
  4. 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

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/281e093b343bdb0c. Report an issue: GitHub.