astrid-runtime/astrid · warning

runtime tree changed while packed admission was in progress

Error message

runtime tree changed while packed admission was in progress

What it means

During packed admission, the kernel scans the runtime tree before and after admitting it, and requires the receipt digest to be stable. If `RuntimeTreeReceipt::from_entries` computed from the pre-scan does not match the post-scan entries, the tree was mutated concurrently and the operation aborts with WouldBlock instead of persisting a torn view.

Solutions

  1. Retry the admission operation once the tree is quiescent (WouldBlock signals a safe retry)
  2. Close other running instances of the app before admitting
  3. Pause file sync/indexing tools that touch the home directory
  4. Serialize home-mutating operations through a single process/lock
Defensive patterns

Strategy: retry

Try / catch

match err.downcast_ref::<io::Error>() {
    Some(e) if e.kind() == io::ErrorKind::WouldBlock => {
        std::thread::sleep(Duration::from_millis(200));
        retry_admit()?; // safe: nothing was persisted
    }
    _ => return Err(err),
}

Prevention

When it happens

Trigger: `admit_blocking` (called via `admit`) observes `!before.matches_entries(&after)` — files were added/removed/modified in the runtime tree between the two scans, including the test-only source mutation hook.

Common situations: Another app instance or CLI command writing to the home concurrently; a sync tool changing files mid-admission; the user moving files while the app starts up.

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


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

Appendix: source

Thrown at crates/astrid-kernel/src/runtime_tree_admit.rs:101

fn admit_blocking(home: &AstridHome, store: &RuntimePrincipalStore) -> io::Result<()> {
    let entries = scan(store, home.root())?;
    let receipt_path = home.migrations_dir().join(RECEIPT_NAME);
    if let Some(receipt) = read_receipt(&receipt_path)?
        && receipt.matches_entries(&entries)
        && catalog_contains_entries(store, &entries)?
        && catalog_contains_receipt(store, &receipt_path)?
    {
        return Ok(());
    }

    store
        .admit_runtime_tree(home.root())
        .map_err(storage_error)?;
    #[cfg(test)]
    run_source_mutation_hook(home)?;
    let after = scan(store, home.root())?;
    if !RuntimeTreeReceipt::from_entries(&entries).matches_entries(&after) {
        return Err(io::Error::new(
            io::ErrorKind::WouldBlock,
            "runtime tree changed while packed admission was in progress",
        ));
    }
    let receipt = RuntimeTreeReceipt::from_entries(&after);
    let bytes = serde_json::to_vec(&receipt).map_err(io::Error::other)?;
    astrid_core::platform_fs::ensure_private_directory(&home.migrations_dir())?;
    astrid_core::platform_fs::atomic_write_private_file(&receipt_path, &bytes)?;
    admit_receipt(store, &receipt_path)?;
    store
        .establish_runtime_projection_receipt(home)
        .map_err(storage_error)
}

fn admit_receipt(store: &RuntimePrincipalStore, receipt_path: &Path) -> io::Result<()> {
    let name = ContentName::new(RECEIPT_RELATIVE_PATH.to_owned()).map_err(io::Error::other)?;
    let bytes = fs::metadata(receipt_path)?.len();
    store

View on GitHub (pinned to affd8760f4)