unicity-aos/aos-ce · warning

grant pending read error for capsule_id

Error message

grant pending read error for capsule_id '{capsule_id}', failing closed: {e}

What it means

This is a warning log emitted in `take_grant_pending` when reading a pending grant record from the key-value store for a capsule fails (kv::take/get returns Err). The broker deliberately 'fails closed': it treats the unknown state as if no pending grant exists (returns false), so the operation is denied or the caller is re-prompted rather than silently approved. The log is diagnostic — the error value `e` carries the underlying storage failure.

Solutions

  1. Inspect the underlying error `e` in the log to identify the kv backend failure (IO, lock, deserialization) and fix that root cause.
  2. Check kv store health/integrity (e.g. reopen or repair the store) and verify the capsule's pending-grant key can be read.
  3. Confirm no concurrent broker process is contending for the same grant record; ensure single-writer or proper locking.
  4. If the record is corrupt, delete the stale pending-grant key so the capsule can re-request a fresh grant (fail-closed means the user will be prompted again).
Defensive patterns

Strategy: fallback

Validate before calling

// Before relying on take_grant_pending, probe readability:
fn grant_pending_readable(capsule_id: &str) -> bool {
    match kv::get_bytes_opt(&grant_pending_key(capsule_id)) {
        Ok(_) => true,
        Err(e) => { log::warn!("grant store unreadable: {e}"); false }
    }
}

Try / catch

match kv::take_bytes(&key) {
    Ok(Some(b)) => parse(b),
    Ok(None) => /* no pending grant */,
    Err(e) => { log::warn!("failing closed: {e}"); /* treat as no grant, deny */ }
}

Prevention

When it happens

Trigger: Calling take_grant_pending(capsule_id) when the underlying kv backend returns Err on the read/take of the pending-grant key — e.g. storage corruption, backend I/O failure, lock contention, or a malformed record the store cannot deserialize.

Common situations: KV store backend temporarily unavailable or restarting; corrupted grant record left by a crashed broker; permission or IO errors on the embedded store; concurrent take by another broker instance that leaves the record in a bad state.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/51800cdbd93a5358. Report an issue: GitHub.

Appendix: source

Thrown at crates/aos-mcp-broker/src/execute.rs:588

/// confirmed-present marker is logged but reported as consumed.
pub(crate) fn take_grant_pending(principal: &str, capsule_id: &str) -> bool {
    let Some(key) = grant_pending_key(principal, capsule_id) else {
        return false;
    };
    match kv::get_bytes_opt(&key) {
        Ok(Some(_)) => {
            if let Err(e) = kv::delete(&key) {
                log::warn(format!(
                    "{}: failed to clear grant pending marker for capsule_id \
                     '{capsule_id}': {e}",
                    crate::profile::log_tag()
                ));
            }
            true
        }
        Ok(None) => false,
        Err(e) => {
            log::warn(format!(
                "{}: grant pending read error for capsule_id '{capsule_id}', \
                 failing closed: {e}",
                crate::profile::log_tag()
            ));
            false
        }
    }
}

/// Confused-deputy guard for state-mutating broker calls.
///
/// `source_id` is the kernel-set UUID of the capsule that originated the
/// inbound IPC message ([`astrid_sdk::runtime::caller`] →
/// `CallerContext::source_id`). It is NOT guest-settable — the kernel
/// stamps it from the publishing capsule's invocation context, so a
/// malicious guest cannot forge it the way it could forge a body field.
/// An ingress is trusted iff the per-(principal, source_id) KV key
/// `mcp.ingress.trust.<source_id>` exists — written ONLY by

View on GitHub (pinned to f6f22024fb)