astrid-runtime/astrid · error

exhausted unique names for quarantined leftover capsule…

Error message

exhausted unique names for quarantined leftover capsule authority receipts

What it means

unique_quarantine_path generates collision-free names for quarantined leftover receipts (encoding the original name and appending a numeric suffix). If it exhausts all candidate names without finding a free path, it bails with this error, refusing to overwrite an existing quarantined file.

Solutions

  1. Clean out stale entries in the migrations quarantine directory (review each file first — they are receipts), then re-run
  2. Manually rename existing quarantined files to shorter names to free candidate names
  3. If the original file name is extremely long, rename the leftover receipt to a short name before re-running the operation
  4. Do not delete or modify quarantined receipts that look unfamiliar; archive the whole quarantine dir and start fresh

Example fix

// before
~/.astrid/migrations/quarantine/ filled with receipt.json, receipt.json.1, ... receipt.json.N
error: exhausted unique names for quarantined leftover capsule authority receipts
// after
$ mkdir ~/astrid-quarantine-archive
$ mv ~/.astrid/migrations/quarantine/* ~/astrid-quarantine-archive/
$ astrid migrate  # quarantine slot freed -> succeeds
Defensive patterns

Strategy: validation

Validate before calling

let qdir = home.migrations_dir().join("quarantine");
let collisions = std::fs::read_dir(&qdir)?.filter_map(Result::ok)
    .filter(|e| e.file_name().to_string_lossy().starts_with("receipt"))
    .count();
if collisions > 1000 { anyhow::bail!("quarantine dir saturated; clean it first"); }

Try / catch

match quarantine_legacy_authority_receipt(&home, &path) {
    Err(e) if e.to_string().contains("exhausted unique names") => {
        // archive existing quarantine contents, then retry
    },
    other => other?,
}

Prevention

When it happens

Trigger: Calling quarantine_legacy_authority_receipt → unique_quarantine_path when the quarantine directory already contains files occupying every generated candidate name for a given receipt name (leftover.rs:288) — typically after many repeated quarantines of identically named receipts, or name-length truncation causing collisions.

Common situations: Repeated migration attempts quarantining the same-named receipt hundreds of times; a very long original file name forcing heavy truncation in encoded_file_name so all suffix variants collide; corrupted quarantine dir full of stale entries.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-capsule-install/src/authority/leftover.rs:288

fn unique_quarantine_path(root: &Path, file_name: &std::ffi::OsStr) -> anyhow::Result<PathBuf> {
    let encoded = encoded_file_name(file_name);
    for index in 0_u32..1024 {
        let candidate = if index == 0 {
            root.join(&encoded)
        } else {
            root.join(format!("{encoded}-{index}"))
        };
        match fs::symlink_metadata(&candidate) {
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(candidate),
            Ok(_) => {},
            Err(error) => {
                return Err(error).with_context(|| {
                    format!("inspect quarantine candidate {}", candidate.display())
                });
            },
        }
    }
    bail!("exhausted unique names for quarantined leftover capsule authority receipts")
}

fn encoded_file_name(name: &std::ffi::OsStr) -> String {
    const MAX_SAFE_NAME: usize = 80;
    match name.to_str() {
        Some(text)
            if !text.is_empty()
                && text.len() <= MAX_SAFE_NAME
                && text != "."
                && text != ".."
                && text
                    .chars()
                    .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.')) =>
        {
            text.to_owned()
        },
        _ => format!("invalid-{}", blake3::hash(&os_str_bytes(name)).to_hex()),
    }

View on GitHub (pinned to affd8760f4)