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

invalid layout migration record

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.

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.

Appendix: 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)