astrid-runtime/astrid · error

leftover capsule authority receipt changed before retirement

Error message

leftover capsule authority receipt changed before retirement: {}

What it means

retire_unmatched_authority_receipt_file re-reads the leftover receipt immediately before fs::remove_file and compares it to the expected bytes captured earlier. If they differ, the file was mutated between ingestion/quarantine and retirement, so deleting it would destroy data the library cannot vouch for — hence the error.

Source

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

            path.display()
        )
    })?;
    if metadata.file_type().is_symlink() || !metadata.is_file() {
        bail!(
            "leftover capsule authority receipt is not a regular file: {}",
            path.display()
        );
    }
    astrid_core::platform_fs::verify_no_redirects(path).with_context(|| {
        format!(
            "verify leftover capsule authority receipt {}",
            path.display()
        )
    })?;
    let actual = fs::read(path)
        .with_context(|| format!("read leftover capsule authority {}", path.display()))?;
    if actual != expected_bytes {
        bail!(
            "leftover capsule authority receipt changed before retirement: {}",
            path.display()
        );
    }
    fs::remove_file(path).with_context(|| {
        format!(
            "retire leftover capsule authority receipt {}",
            path.display()
        )
    })?;
    if let Some(parent) = path.parent() {
        sync_authority_directory(parent)?;
    }
    Ok(())
}

fn unique_relocated_receipt(
    home: &AstridHome,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Close other processes and re-run the operation so the read-then-delete window is uncontended
  2. Re-run from scratch: the tool re-reads the (now stable) receipt and retires it against fresh expected bytes
  3. If the change was intentional, re-run ingestion so the new bytes become the expected bytes before retirement
  4. Check filesystem/journal health if corruption rather than concurrent writes is suspected

Example fix

// before
$ astrid migrate &  # plus a second instance editing the receipt
error: leftover capsule authority receipt changed before retirement
// after
$ pkill -f astrid
$ astrid migrate  # single process, stable file -> succeeds
Defensive patterns

Strategy: validation

Validate before calling

let before = std::fs::read(path)?;
// pass `before` as expected_bytes and ensure no other process writes between read and retire
assert!(retire_unmatched_authority_receipt_file(path, &before).is_ok());

Type guard

fn receipt_unchanged(p: &Path, expected: &[u8]) -> bool {
    std::fs::read(p).map(|a| a == expected).unwrap_or(false)
}

Try / catch

if let Err(e) = retire_unmatched_authority_receipt_file(path, &expected) {
    if e.to_string().contains("changed before retirement") {
        // re-read fresh bytes and re-ingest before retiring
        let fresh = std::fs::read(path)?;
        // re-ingest(fresh) then retire against fresh
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling retire_one_leftover → retire_unmatched_authority_receipt_file when the file content at leftover.rs:167-174 changed after the expected bytes were captured: a concurrent writer (another tool instance, sync daemon, editor) modified the receipt during the ingest-then-retire window.

Common situations: Two astrid processes running concurrently on the same home; a text editor or sync tool auto-saving the receipt; antivirus quarantining/rewriting the file mid-operation.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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