astrid-runtime/astrid · error
layout cutover record is not a regular file: {}
Error message
layout cutover record is not a regular file: {} What it means
A completed layout v1-to-v2 migration must be backed by the durable cutover records `layout-v1-to-v2.intent` and `layout-v1-to-v2.complete` in the migrations directory. require_layout_provenance found one of these records missing (or reachable only through a redirect) and rejects the ledger with InvalidData. This prevents a fabricated or partially restored migrations directory from authorizing the layout finalizer.
Source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:203
stack.push(path);
}
}
Ok(targets)
}
/// A completed v2 ledger must be tied either to the explicit fresh-home
/// disposition or to the durable layout cutover intent and receipt. This
/// check runs before stores open, so a canonical but invented component list
/// cannot authorize the legacy layout finalizer.
pub(super) fn require_layout_provenance(migrations: &Path, fresh_layout: bool) -> io::Result<()> {
if fresh_layout {
return Ok(());
}
for name in ["layout-v1-to-v2.intent", "layout-v1-to-v2.complete"] {
let path = migrations.join(name);
let metadata = fs::symlink_metadata(&path).map_err(|error| {
if error.kind() == io::ErrorKind::NotFound {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"layout-two ledger has no durable cutover record: {}",
path.display()
),
)
} else {
error
}
})?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"layout cutover record is not a regular file: {}",
path.display()
),
));View on GitHub (pinned to affd8760f4)
Solutions
- Restore both `layout-v1-to-v2.intent` and `layout-v1-to-v2.complete` from a trusted backup of the migrations directory.
- If this home never completed the v1-to-v2 migration, complete or redo the migration so the records are written durably.
- Set the fresh-layout disposition explicitly if this is genuinely a brand-new home, so the records are not required.
- Verify you are pointing at the correct home directory; a wrong ASTRID_HOME looks identical to missing records.
- Never recreate the records by hand — they carry cryptographic provenance; hand-made copies will fail later digest checks.
Example fix
// before mv ~/.astrid/migrations/layout-v1-to-v2.complete /tmp/ # hand-editing bookkeeping // after # restore from backup or rerun the migration to regenerate both records cp backup/migrations/layout-v1-to-v2.* ~/.astrid/migrations/
Defensive patterns
Strategy: validation
Validate before calling
use std::fs;
fn cutover_records_present(migrations: &std::path::Path) -> std::io::Result<bool> {
for name in ["layout-v1-to-v2.intent", "layout-v1-to-v2.complete"] {
match fs::symlink_metadata(migrations.join(name)) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(e) => return Err(e),
}
}
Ok(true)
} Try / catch
if let Err(e) = require_layout_provenance(migrations, fresh_layout) {
if e.to_string().contains("no durable cutover record") {
// restore records from backup or rerun the migration
return Err(MigrationError::MissingProvenance);
}
return Err(e.into());
} Prevention
- Back up the entire migrations directory, not just data directories
- Never delete or move files under migrations/ by hand
- Confirm ASTRID_HOME points at the intended home before upgrading
When it happens
Trigger: Calling reject_incomplete_layout_v2 / require_layout_provenance with fresh_layout=false when either `layout-v1-to-v2.intent` or `layout-v1-to-v2.complete` is absent under the migrations directory (NotFound is remapped here), or when symlink_metadata of the record path itself fails. The distinct 'not a regular file' variant at line 218 fires when the record exists but is a symlink or non-file.
Common situations: Restoring a home directory from a partial backup that omitted the migrations dir; users hand-deleting migration bookkeeping files; a crashed first run that never wrote both records; pointing the app at the wrong (fresh) home while a v2 ledger exists elsewhere.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- legacy capsule entry has a non-UTF-8 name
- layout cutover record is empty: {}
- legacy source changed before retirement: {}
- legacy retirement root is not a directory: {}
- legacy source is an active mount: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/9f443169e9b4f22c.
Report an issue: GitHub.