astrid-runtime/astrid · error · io::Error

invalid layout migration record {}: {error}

Error message

invalid layout migration record {}: {error}

What it means

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.

Source

Thrown at crates/astrid-core/src/dirs_layout_records.rs:119

        "astrid home layout migration transaction v1",
        &material_bytes,
    )))
}

pub(super) fn admit_or_write_canonical<T>(
    path: &Path,
    expected: &T,
    allow_create: bool,
) -> 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(())
        },

View on GitHub (pinned to affd8760f4)

Solutions

  1. Inspect the file named in the message and fix or remove the corrupt record (it will be rewritten by allow_create paths).
  2. Delete the invalid record file and re-run the migration from a clean state, ensuring the layout directories are otherwise consistent.
  3. Restore the record from backup if the migration must be continued transactionally.
  4. Do not hand-edit layout records; let the library serialize them canonically.

Example fix

// before: hand-edited / truncated record
{ "version": 2, "status": "begin"   // truncated
// after: delete and regenerate
std::fs::remove_file(record_path)?;
begin_layout_v2_migration(&dir)?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn record_parses<T: serde::de::DeserializeOwned>(path: &Path) -> bool {
    std::fs::read(path)
        .map(|b| serde_json::from_slice::<T>(&b).is_ok())
        .unwrap_or(false)
}

Try / catch

match result {
    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().starts_with("invalid layout migration record") => {
        // quarantine/delete the corrupt record and re-run the migration
    },
    other => other?,
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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