astrid-runtime/astrid · error · io::Error
layout-two home contains legacy state without a completion r
Error message
layout-two home contains legacy state without a completion receipt
What it means
retire_verified_legacy_source (reached via complete_layout_v2 on an already-v2 home) found no completion receipt in var/migrations/, yet legacy state (state.db, cow/, or the migration intent) still exists. A v2 home without a receipt but with legacy leftovers is an inconsistent state the library will not clean up, because it cannot verify what the legacy state belongs to. Returned as InvalidData.
Source
Thrown at crates/astrid-core/src/dirs_layout.rs:337
self.state_db_path(),
];
for path in paths {
verify_existing_ancestor(&path)?;
}
validate_legacy_retirement_candidate(&self.cow_dir())?;
Ok(())
}
pub(super) fn retire_verified_legacy_source(&self) -> io::Result<()> {
let receipt_path = self.migrations_dir().join(LAYOUT_MIGRATION_RECEIPT);
let intent_path = self.migrations_dir().join(LAYOUT_MIGRATION_INTENT);
match std::fs::symlink_metadata(&receipt_path) {
Err(error) if error.kind() == io::ErrorKind::NotFound => {
if path_entry_present(&self.state_db_path())?
|| path_entry_present(&self.cow_dir())?
|| path_entry_present(&intent_path)?
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"layout-two home contains legacy state without a completion receipt",
));
}
return Ok(());
},
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"layout migration receipt is redirected or not a regular file: {}",
receipt_path.display()
),
));
},
Ok(_) => {},
Err(error) => return Err(error),
}View on GitHub (pinned to affd8760f4)
Solutions
- Restore var/migrations/ (especially layout-v1-to-v2.complete) from backup so the receipt again matches the surviving legacy state
- If the legacy state.db and cow/ are genuinely obsolete, verify the astrid.volume is healthy and then remove state.db, cow/, and the intent file manually — only after confirming the data was migrated
- Re-run the full v1→v2 migration from the legacy sentinel state instead of forcing retirement of an unverifiable home
- Do not delete receipt/intent files to retry migration; the retirement path is content-bound to them by design
Example fix
// before: receipt deleted but state.db remains
home.complete_layout_v2(&target)?; // InvalidData: legacy state without receipt
// after: restore the receipt, or remove verified-obsolete legacy state
std::fs::remove_file("~/.astrid/var/state.db")?; // only if data is confirmed migrated
std::fs::remove_dir_all("~/.astrid/var/cow")?;
home.complete_layout_v2(&target)?; Defensive patterns
Strategy: validation
Validate before calling
let receipt = home.migrations_dir().join("layout-v1-to-v2.complete");
let legacy_left = home.principal_store_path_parent_state_present(); // or check state.db/cow directly
if !receipt.exists() && (state_db.exists() || cow.exists()) {
return Err(anyhow!("v2 home has legacy state but no receipt; restore or clean first"));
} Type guard
fn retirement_is_safe(receipt: &Path, legacy: &[&Path]) -> bool {
receipt.is_file() || legacy.iter().all(|p| !p.exists())
} Try / catch
match home.complete_layout_v2(&target) {
Err(e) if e.to_string().contains("legacy state without a completion receipt") => {
// restore receipt from backup or manually remove verified-obsolete legacy state
},
r => r?,
} Prevention
- Never delete files under var/migrations/ to 'reset' migration state
- Restore backups as complete sets (etc/ + var/migrations/ together)
- Verify astrid.volume integrity before touching legacy state
- Let the normal startup/retirement path clean legacy state; avoid manual surgery
When it happens
Trigger: Calling complete_layout_v2 on a home whose sentinel already says v2, where var/migrations/layout-v1-to-v2.complete is missing but state.db, cow/, or layout-v1-to-v2.intent still exist — e.g. after a botched manual cleanup or restore that deleted the receipt but not the legacy data.
Common situations: Restoring a partial backup (receipt lost, legacy files kept); manually deleting files in var/migrations/ to 'reset' migration state; disk loss corrupting var/migrations/ while state.db survived; copying a half-migrated home.
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
- legacy capsule {id} authority verification produced no recei
- layout migration receipt does not match its intent or destin
- legacy capsule authority identity differs for {id}
- legacy capsule {id} authority receipt disappeared
- unsupported Astrid home layout version {other:?}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/6ad9f4d1ff8749da.
Report an issue: GitHub.