unicity-aos/aos-ce · error
grant pending read error for capsule_id
Error message
grant pending read error for capsule_id '{capsule_id}', surfacing a fresh prompt: {e} What it means
grant_pending reads the KV grant-pending record for a capsule_id to decide whether to resume a pending grant prompt. On a read error (not a clean miss), it logs this warning and returns false, which surfaces a fresh prompt to the user instead of resuming — a fail-open-to-fresh-prompt behavior to avoid getting stuck.
Solutions
- Restore KV availability per the {e} detail and retry the grant flow; the user will be re-prompted either way.
- Delete the stale/corrupt pending key for that capsule_id so subsequent reads are clean misses.
- Check for version drift between the writer and reader of the pending record schema.
- Accept the fresh-prompt behavior if the pending grant was stale; complete the new grant flow.
Example fix
// before
Err(e) => {
log::warn(format!("{}: grant pending read error for capsule_id '{capsule_id}', \
surfacing a fresh prompt: {e}", crate::profile::log_tag()));
false
}
// after
// clear stale record then retry:
kv::delete(&grant_key(capsule_id)).ok();
Err(e) => {
log::warn(format!("{}: grant pending read error for capsule_id '{capsule_id}', \
surfacing a fresh prompt: {e}", crate::profile::log_tag()));
false
} Defensive patterns
Strategy: try-catch
Validate before calling
// probe grant state store before initiating a grant flow
const reachable = await kv.ping();
if (!reachable) throw new Error('kv unavailable; grant will surface a fresh prompt'); Try / catch
match kv::get(&grant_key) {
Ok(Some(v)) => resume_grant(v),
Ok(None) => surface_fresh_prompt(),
Err(e) => { log::warn("grant pending read error: {e}"); surface_fresh_prompt() }
} Prevention
- Avoid racing kv::delete with concurrent grant readers
- Version the grant-pending record schema
- Treat a fresh prompt as the safe recovery path on read errors
When it happens
Trigger: KV read of the grant-pending entry for capsule_id returns Err in grant_pending — storage backend error, value decode failure, or I/O problem on the record; also reachable after a prior flow's kv::delete raced with the read.
Common situations: KV outage mid-grant flow; pending record written by an older broker version that no longer deserializes; concurrent grant flows deleting the key while another reads it.
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
- ingress pending read error for source_id
- aos-mcp: could not retire
- aos-mcp: could not audit rejected hook message
- grant pending read error for capsule_id
- grant decision read error for capsule_id
AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13).
Data as JSON: /api/errors/3563d461462e3c68.
Report an issue: GitHub.
Appendix: source
Thrown at crates/aos-mcp-broker/src/execute.rs:548
let Some(key) = grant_pending_key(principal, capsule_id) else {
return false;
};
match kv::get_bytes_opt(&key) {
Ok(Some(bytes)) => {
if marker_is_fresh(&bytes) {
true
} else {
// Stale (or unparseable / clock-unavailable) marker: the paired
// respond never cleared it. Treat as not pending and best-effort
// delete so the next ungranted call re-prompts — the self-heal
// that keeps a dropped respond from wedging the pair forever.
let _ = kv::delete(&key);
false
}
}
Ok(None) => false,
Err(e) => {
log::warn(format!(
"{}: grant pending read error for capsule_id '{capsule_id}', \
surfacing a fresh prompt: {e}",
crate::profile::log_tag()
));
false
}
}
}
/// Consume the outstanding grant-consent prompt marker for
/// `(principal, capsule_id)`, returning whether one existed.
///
/// Called by [`crate::approval::handle_mcp_grant_respond`] on BOTH approve and
/// deny so the marker is single-use and can never stick: a declined prompt must
/// not leave a marker that suppresses every future grant prompt for the pair.
/// The return value is informational (the grant itself is driven by the
/// published decision, not this marker); clearing the marker is the effect that
/// matters here.View on GitHub (pinned to f6f22024fb)