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
- 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.
- 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.
- Check whether a service is still writing tokens/kv entries into the legacy layout and stop it before migrating.
- 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
- Stop all processes writing to the legacy layout before migrating
- Export/drain legacy kv and token state as part of the upgrade runbook
- Run migration during a maintenance window so nothing repopulates legacy dirs
- Check for non-default aliases with remaining legacy data before upgrading
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
- absent migration source has a digest
- decode migration ledger
- durable capsule disappeared after publish
- durable capsule failed authoritative verification
- historical principal home receipt is missing or changed
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)