{"record":{"id":"3563d461462e3c68","repo":"unicity-aos/aos-ce","slug":"grant-pending-read-error-for-capsule-id-capsule-id-surfacing","errorCode":null,"errorMessage":"grant pending read error for capsule_id '{capsule_id}', surfacing a fresh prompt: {e}","messagePattern":"grant pending read error for capsule_id '(.+?)', surfacing a fresh prompt: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/aos-mcp-broker/src/execute.rs","lineNumber":548,"sourceCode":"    let Some(key) = grant_pending_key(principal, capsule_id) else {\n        return false;\n    };\n    match kv::get_bytes_opt(&key) {\n        Ok(Some(bytes)) => {\n            if marker_is_fresh(&bytes) {\n                true\n            } else {\n                // Stale (or unparseable / clock-unavailable) marker: the paired\n                // respond never cleared it. Treat as not pending and best-effort\n                // delete so the next ungranted call re-prompts — the self-heal\n                // that keeps a dropped respond from wedging the pair forever.\n                let _ = kv::delete(&key);\n                false\n            }\n        }\n        Ok(None) => false,\n        Err(e) => {\n            log::warn(format!(\n                \"{}: grant pending read error for capsule_id '{capsule_id}', \\\n                 surfacing a fresh prompt: {e}\",\n                crate::profile::log_tag()\n            ));\n            false\n        }\n    }\n}\n\n/// Consume the outstanding grant-consent prompt marker for\n/// `(principal, capsule_id)`, returning whether one existed.\n///\n/// Called by [`crate::approval::handle_mcp_grant_respond`] on BOTH approve and\n/// deny so the marker is single-use and can never stick: a declined prompt must\n/// not leave a marker that suppresses every future grant prompt for the pair.\n/// The return value is informational (the grant itself is driven by the\n/// published decision, not this marker); clearing the marker is the effect that\n/// matters here.","sourceCodeStart":530,"sourceCodeEnd":566,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/crates/aos-mcp-broker/src/execute.rs#L530-L566","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nErr(e) => {\n    log::warn(format!(\"{}: grant pending read error for capsule_id '{capsule_id}', \\\n         surfacing a fresh prompt: {e}\", crate::profile::log_tag()));\n    false\n}\n// after\n// clear stale record then retry:\nkv::delete(&grant_key(capsule_id)).ok();\nErr(e) => {\n    log::warn(format!(\"{}: grant pending read error for capsule_id '{capsule_id}', \\\n         surfacing a fresh prompt: {e}\", crate::profile::log_tag()));\n    false\n}","handlingStrategy":"try-catch","validationCode":"// probe grant state store before initiating a grant flow\nconst reachable = await kv.ping();\nif (!reachable) throw new Error('kv unavailable; grant will surface a fresh prompt');","typeGuard":null,"tryCatchPattern":"match kv::get(&grant_key) {\n    Ok(Some(v)) => resume_grant(v),\n    Ok(None) => surface_fresh_prompt(),\n    Err(e) => { log::warn(\"grant pending read error: {e}\"); surface_fresh_prompt() }\n}","preventionTips":["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"],"tags":["kv","grant","broker","state"],"backgroundTag":"database-query-failed","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}