astrid-runtime/astrid · error

legacy principal retains unsupported state; no…

Error message

legacy principal {alias} retains unsupported {name} state; no authoritative migration API exists: {}

What it means

Before migrating a legacy layout, the library scans each non-default principal alias's legacy `kv` and `tokens` directories. If such a directory exists and its snapshot shows a non-zero entry count, migration is refused with `io::ErrorKind::Unsupported` because the library has no authoritative, lossless migration path for that legacy state. This is a deliberate safety barrier: migrating would silently discard data.

Solutions

  1. Drain/export the legacy kv and tokens state for the affected alias using the legacy APIs so the snapshot becomes empty, then re-run migration.
  2. If the data is intentionally abandoned, remove the legacy `kv`/`tokens` directories for that alias (after confirming nothing needs them) so the path check is skipped.
  3. Check whether a service is still writing tokens/kv entries into the legacy layout and stop it before migrating.
  4. Consult release notes for a supported migration tool for the legacy state instead of hand-migrating.

Example fix

// before: legacy tokens dir still populated
//   homes/<alias>/tokens/  (3 entries) -> Unsupported error
// after: drain legacy state first
$ astrid legacy export-tokens --alias work > tokens.json
$ astrid legacy clear --alias work
$ astrid migrate
Defensive patterns

Strategy: validation

Validate before calling

// Before migrating, ensure legacy kv/tokens dirs are absent or empty
fn legacy_sources_drained(home: &Path, alias: &str) -> io::Result<()> {
    for sub in ["kv", "tokens"] {
        let dir = home.join(alias).join(sub);
        if dir.exists() && std::fs::read_dir(&dir)?.next().is_some() {
            return Err(io::Error::new(io::ErrorKind::Unsupported, format!("{dir:?} still has entries")));
        }
    }
    Ok(())
}

Try / catch

match migrate_legacy_layout(&home) {
    Err(e) if e.kind() == io::ErrorKind::Unsupported => {
        // inspect e.to_string() for which alias/source retains state; drain it and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `migrate_legacy_layout` (or the tested paths `empty_non_default_audit_is_retired_instead_of_refusing_cutover` / `non_empty_non_default_audit_is_quarantined_with_bytes_preserved` trigger it via the same check) when a legacy principal alias still holds entries in `kv_dir()` or `tokens_dir()` whose `snapshot_path` reports `entries != SourceCount::ZERO`.

Common situations: Upgrading an old astrid home where a secondary principal still has kv pairs or auth tokens stored in the legacy layout; switching to a version that enforces the migration barrier before the legacy data was drained; automation creating tokens in the legacy directory between upgrade steps.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs:519

) -> io::Result<()> {
    for (alias, uid) in directory.bindings() {
        if alias != PrincipalId::default() {
            handle_non_default_audit_source(
                home,
                &alias,
                snapshots.get(&format!("principal:{uid}:audit")),
            )?;
        }
        for (name, path) in [
            ("kv", home.principal_home(&alias).kv_dir()),
            ("tokens", home.principal_home(&alias).tokens_dir()),
        ] {
            if !path_exists(&path)? {
                continue;
            }
            let snapshot = snapshot_path(&path)?;
            if snapshot.entries != SourceCount::ZERO {
                return Err(io::Error::new(
                    io::ErrorKind::Unsupported,
                    format!(
                        "legacy principal {alias} retains unsupported {name} state; no authoritative migration API exists: {}",
                        path.display()
                    ),
                ));
            }
        }
    }
    Ok(())
}

fn validate_component_name(name: &str) -> io::Result<()> {
    match name {
        "system:state-db"
        | "system:cow"
        | "system:invites"
        | "system:pair-tokens"

View on GitHub (pinned to affd8760f4)