{"record":{"id":"5ca0aca00c8f2b00","repo":"tinyhumansai/openhuman","slug":"secret-request-cancelled-before-user-submit","errorCode":null,"errorMessage":"secret request {} cancelled before user submit","messagePattern":"secret request (.+?) cancelled before user submit","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/openhuman/mcp/registry/setup.rs","lineNumber":155,"sourceCode":"    entry.value = Some(value);\n    entry.last_touched = Instant::now();\n    if let Some(tx) = entry.waiter.take() {\n        let _ = tx.send(());\n    }\n    tracing::debug!(\"[mcp-setup] fulfilled ref={}\", r.as_str());\n    true\n}\n\n/// Block on a freshly-minted request with the global timeout. On timeout\n/// the entry is removed and `Err(_)` is returned.\npub async fn await_fulfillment(r: &SecretRef, rx: oneshot::Receiver<()>) -> anyhow::Result<()> {\n    match timeout(REQUEST_TIMEOUT, rx).await {\n        Ok(Ok(())) => Ok(()),\n        Ok(Err(_)) => {\n            // Sender dropped — usually means GC purged the entry. Surface\n            // as a timeout-style error to keep the caller simple.\n            let _ = forget(r).await;\n            anyhow::bail!(\"secret request {} cancelled before user submit\", r.as_str())\n        }\n        Err(_) => {\n            let _ = forget(r).await;\n            anyhow::bail!(\n                \"secret request {} timed out after {}s\",\n                r.as_str(),\n                REQUEST_TIMEOUT.as_secs()\n            )\n        }\n    }\n}\n\n/// Resolve a `{KEY: SecretRef}` map into a `Vec<(KEY, VALUE)>`. Returns\n/// `Err(_)` if any ref is unknown or not yet fulfilled — callers should\n/// retry rather than partially-apply.\n///\n/// Touches the `last_touched` on every hit so iterative `test_connection`\n/// calls reset the idle TTL.","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/mcp/registry/setup.rs#L137-L173","documentation":"During MCP server install, secrets requested from the user are held in a process-local in-memory map keyed by SecretRef; await_fulfillment blocks on a oneshot receiver with a 5-minute budget (REQUEST_TIMEOUT = 300 s). This variant fires when the sender is dropped without a submit — per the source comment, usually because the GC sweep purged the entry (fulfilled-but-unused entries are purged after a 15-minute idle TTL) — and it is deliberately surfaced as a timeout-style error to keep callers simple. The entry is forgotten on this path, so the old SecretRef is dead.","triggerScenarios":"await_fulfillment is waiting while the pending entry is removed from the map: the idle-GC sweep collects it, a duplicate/superseding request replaces it, or the process is tearing the registry down. Distinct from the timeout path: the wait ended early because the channel closed, not because 300 s elapsed.","commonSituations":"User abandons the credential prompt long enough for GC; two installs of the same server racing so the second registration drops the first's sender; slow UI leaving prompts pending past the GC horizon.","solutions":["Retry the install — a fresh request mints a new SecretRef and re-prompts the user.","Avoid concurrent installs of the same server; let the first flow finish or cancel it cleanly first.","If it recurs, check logs for GC/purge activity around the abort to confirm the entry was collected rather than a bug dropping senders."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"Catch the anyhow error and branch on the message: `cancelled before user submit` → re-run the request flow with a fresh SecretRef (the old ref was forgotten); `timed out` → re-prompt only if a user is actually present. Never retry the dead ref itself.","preventionTips":["Complete secret prompts promptly — entries are GC'd after ~15 min idle.","Serialize installs that need the same secrets; avoid duplicate concurrent requests.","Run installs in a session where the prompt UI is visible."],"tags":["mcp","secrets","install","cancellation"],"backgroundTag":"async-operation-cancelled","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}