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
- Inspect the file named in the message and fix or remove the corrupt record (it will be rewritten by allow_create paths).
- Delete the invalid record file and re-run the migration from a clean state, ensuring the layout directories are otherwise consistent.
- Restore the record from backup if the migration must be continued transactionally.
- 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
- Never hand-edit layout record files
- Ensure writes are atomic (library already stages + renames) so crashes cannot truncate records
- Keep the same crate version across begin/complete of a migration
- Back up records before manual maintenance
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
- tool_describe payload is not valid JSON or has an unexpected
- layout migration record does not match this transaction: {}
- layout migration record is not canonical: {}
- decode migration ledger {}: {error}
- migration ledger is not canonical: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/670316068d3685e0.
Report an issue: GitHub.