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

WouldBlock

WouldBlock

Error message

cannot delete principal {principal}: legacy native source is not retired: {}

What it means

Thrown by ensure_principal_delete_allowed when a principal's legacy native home directory still exists and is non-empty. The kernel refuses to delete or roll back a principal while its legacy (pre-migration) native source has not been retired, because deleting would discard data that has not crossed the migration barrier. The io::ErrorKind::WouldBlock signals a temporary, resolvable block rather than data corruption.

Source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/mod.rs:557

        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
        Err(error) => return Err(error),
    };
    collect_workspace_targets(&capsules)
}

/// Agent deletion/rollback must not discard a released source that has not
/// crossed the global barrier.  This is synchronous by design: the admin
/// delete path already holds its write fence and must make the decision before
/// unlinking identity or purging durable state.
pub(crate) fn ensure_principal_delete_allowed(
    home: &AstridHome,
    principal: &PrincipalId,
) -> io::Result<()> {
    reject_incomplete_layout_v2(home)?;
    let principal_home = home.principal_home(principal);
    let root = principal_home.root();
    if path_exists(root)? && snapshot_path(root)?.entries != 0 {
        return Err(io::Error::new(
            io::ErrorKind::WouldBlock,
            format!(
                "cannot delete principal {principal}: legacy native source is not retired: {}",
                root.display()
            ),
        ));
    }
    let profile = principal_home.config_dir().join("profile.toml");
    if path_exists(&profile)? {
        return Err(io::Error::new(
            io::ErrorKind::WouldBlock,
            format!(
                "cannot delete principal {principal}: legacy profile is not retired: {}",
                profile.display()
            ),
        ));
    }
    Ok(())

View on GitHub (pinned to affd8760f4)

Solutions

  1. Run the legacy migration/cut-over for this principal so its native source is imported and retired, then retry the delete.
  2. Inspect the path printed in the error; if the files are already migrated and safe to drop, archive and remove them (or retire the now-empty directory).
  3. Verify the agent/kernel version is current so legacy writers no longer recreate the home directory.
  4. If deletion must proceed, ensure allow_empty_cleanup/retire path is enabled so empty legacy dirs are cleaned instead of blocking.

Example fix

// before
kernel.delete_principal(&principal)?; // WouldBlock: legacy home not retired
// after
migrate_legacy_principal(&home, &principal)?; // importer retires legacy source
assert!(snapshot_path(principal_home.root())?.entries == 0);
kernel.delete_principal(&principal)?;
Defensive patterns

Strategy: validation

Validate before calling

let root = home.principal_home(principal).root();
if path_exists(root)? && snapshot_path(root)?.entries != 0 {
    // run the legacy migration importer before calling delete
}

Type guard

fn legacy_source_retired(root: &Path) -> io::Result<bool> {
    Ok(!path_exists(root)? || snapshot_path(root)?.entries == 0)
}

Try / catch

match kernel.delete_principal(&principal) {
    Err(e) if e.kind() == io::ErrorKind::WouldBlock => run_legacy_migration_then_retry(),
    other => other?,
}

Prevention

When it happens

Trigger: Calling the admin principal-delete/rollback path (ensure_principal_delete_allowed) while <home>/<principal-home>/root still contains entries; layout-v2 must already be complete (reject_incomplete_layout_v2 passed first).

Common situations: Operators running deletion before the legacy-to-v2 migration importer has drained the principal's home; a partial or failed migration left files behind; an old agent version recreated legacy home files.

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/012ffc3f2c405831. Report an issue: GitHub.