astrid-runtime/astrid · error

cannot retire leftover capsule authority while previous tran

Error message

cannot retire leftover capsule authority while previous transaction artifact exists at {}

What it means

Like the pending check, `unmatched_active_receipts` refuses to sweep when a *previous* receipt-transaction artifact exists. The previous-generation file is the backup of the receipt that a transaction intended to replace; its presence means a transaction has not fully completed (or crashed between commit and cleanup), and retiring leftover receipts now could destroy data needed for recovery.

Source

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

    };
    AuthorityReceiptTransaction::stage(home, target_dir, &receipt)?.commit()?;
    Ok(())
}

/// Active leftover receipts that are not workspace-portal targets.
pub(crate) fn unmatched_active_receipts(
    home: &AstridHome,
    workspace_targets: &[PathBuf],
) -> anyhow::Result<Vec<PathBuf>> {
    let status = super::legacy_authority_receipt_status(home, workspace_targets)?;
    if !status.pending.is_empty() {
        bail!(
            "cannot retire leftover capsule authority while pending transaction artifact exists at {}",
            status.pending[0].display()
        );
    }
    if !status.previous.is_empty() {
        bail!(
            "cannot retire leftover capsule authority while previous transaction artifact exists at {}",
            status.previous[0].display()
        );
    }
    Ok(status.unknown_active)
}

/// Parse a regular-file leftover receipt. Invalid JSON returns `Ok(None)`.
pub(crate) fn parse_legacy_authority_receipt(
    path: &Path,
) -> anyhow::Result<Option<(InstalledAuthority, Vec<u8>)>> {
    let metadata = fs::symlink_metadata(path)
        .with_context(|| format!("inspect leftover capsule authority {}", path.display()))?;
    if metadata.file_type().is_symlink() || !metadata.is_file() {
        bail!(
            "leftover capsule authority receipt is not a regular file: {}",
            path.display()
        );

View on GitHub (pinned to affd8760f4)

Solutions

  1. Complete transaction recovery: restore/commit or safely discard the previous-generation artifact, then rerun the sweep
  2. If no transaction is active and the artifact is stale, remove it per the documented recovery procedure and retry
  3. Serialize the leftover sweep with installs/migrations so transactions can never be mid-flight

Example fix

// before
bail!("cannot retire leftover capsule authority while previous transaction artifact exists at {}", status.previous[0].display());
// after: finish the interrupted transaction first
// AuthorityReceiptTransaction::recover(home)?;
// let leftover = unmatched_active_receipts(home, &workspace_targets)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Check for previous-generation transaction artifacts before sweeping
let status = legacy_authority_receipt_status(&home, &workspace_targets)?;
if !status.previous.is_empty() {
    // finish or discard the interrupted transaction before sweeping
}

Try / catch

match unmatched_active_receipts(&home, &targets) {
    Err(e) if e.to_string().contains("previous transaction artifact") => {
        AuthorityReceiptTransaction::recover(&home)?;
        unmatched_active_receipts(&home, &targets)
    },
    other => other,
}

Prevention

When it happens

Trigger: Calling `unmatched_active_receipts` (via `retire_unmatched_legacy_authority_receipts`) while `legacy_authority_receipt_status(...).previous` is non-empty — a `.previous` transaction artifact remains at the reported path, typically from an interrupted or crashed receipt commit.

Common situations: Crash or SIGKILL during the two-phase receipt transaction leaving the previous-generation file uncleaned; disk-full during commit cleanup; concurrent install/migration racing the sweep.

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