astrid-runtime/astrid · error

gateway revocation migration receipt raced; retry startup

Error message

gateway revocation migration receipt raced; retry startup

What it means

Race guard in migrate_legacy_file: the compare_and_swap that installs the revocation migration receipt failed because another writer inserted a different receipt concurrently. The digest-bearing receipt encodes the migrated entry set; a mismatch means two different migration attempts raced, the on-disk state is ambiguous, and startup must be retried rather than proceeding on possibly-divergent revocation data.

Solutions

  1. Retry startup — the winner's receipt should now be readable and migration complete
  2. Verify the receipt and hydrated epochs are present before continuing
  3. Ensure only one instance performs startup migration, or rely on the CAS fence
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at crates/astrid-gateway/src/revocations.rs:424 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at crates/astrid-gateway/src/revocations.rs:424

        schema: 1,
        digest,
        principal_count: entries.len(),
    };
    let encoded = serde_json::to_vec(&receipt).context("encode revocation migration receipt")?;
    let existing = store
        .get(REVOCATION_NAMESPACE, MIGRATION_RECEIPT_KEY)
        .await
        .map_err(|error| anyhow::anyhow!("read revocation migration receipt: {error}"))?;
    if let Some(existing) = existing {
        if existing != encoded {
            anyhow::bail!("gateway revocation migration receipt conflicts");
        }
    } else if !store
        .compare_and_swap(REVOCATION_NAMESPACE, MIGRATION_RECEIPT_KEY, None, encoded)
        .await
        .map_err(|error| anyhow::anyhow!("write revocation migration receipt: {error}"))?
    {
        anyhow::bail!("gateway revocation migration receipt raced; retry startup");
    }
    let _ = load_from_store(store).await?;
    match std::fs::remove_file(&path) {
        Ok(()) => {},
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {},
        Err(error) => return Err(anyhow::anyhow!("retire {}: {error}", path.display())),
    }
    Ok(true)
}

/// Spawn the audit-event watcher. Subscribes to the kernel's audit
/// topic and updates the revocation map whenever a successful
/// `AgentDelete` admin op lands. Detached: terminates when the bus
/// is dropped (i.e. daemon shutdown), so no explicit join is needed.
///
/// `bus` is the kernel's shared event bus; `revoked_at` is the same
/// `Arc<RwLock<…>>` held by [`crate::state::GatewayState`] so writes
/// here become visible to every in-flight verify call.

View on GitHub (pinned to affd8760f4)