{"record":{"id":"46b4777cc0738b19","repo":"zeroclaw-labs/zeroclaw","slug":"user-denied-authorization","errorCode":null,"errorMessage":"User denied authorization","messagePattern":"User denied authorization","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/auth/gemini_oauth.rs","lineNumber":307,"sourceCode":"\n            return Ok(TokenSet {\n                access_token: token_response.access_token,\n                refresh_token: token_response.refresh_token,\n                id_token: token_response.id_token,\n                expires_at,\n                token_type: token_response.token_type.or_else(|| Some(\"Bearer\".into())),\n                scope: token_response.scope,\n            });\n        }\n\n        if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&body) {\n            match err.error.as_str() {\n                \"authorization_pending\" => {}\n                \"slow_down\" => {\n                    tokio::time::sleep(Duration::from_secs(5)).await;\n                }\n                \"access_denied\" => {\n                    anyhow::bail!(\"User denied authorization\");\n                }\n                \"expired_token\" => {\n                    anyhow::bail!(\"Device code expired\");\n                }\n                _ => {\n                    anyhow::bail!(\n                        \"Google OAuth error: {} - {}\",\n                        err.error,\n                        err.error_description.unwrap_or_default()\n                    );\n                }\n            }\n        }\n    }\n}\n\n/// Receive OAuth code via loopback callback OR manual stdin input.\n/// If the callback server can't receive the redirect (e.g., remote/headless environment),","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/auth/gemini_oauth.rs#L289-L325","documentation":"While polling the Google token endpoint during the device flow, the response carried error=access_denied. Google returns this when the user actively rejects the consent prompt (clicks Cancel/Deny) or when the app is blocked from consent. The poll loop maps that specific error code to this bail, ending the flow immediately.","triggerScenarios":"poll_device_code_tokens receives an OAuthErrorResponse whose error field is exactly \"access_denied\" after the user clicked deny on the Google consent screen, or after an admin policy auto-denied the OAuth client.","commonSituations":"User changes their mind at the consent screen; the Google Cloud OAuth app is unverified and the user backs out of the warning screen; a workspace admin disabled third-party access so consent is denied automatically.","solutions":["Re-run auth login --device-code and click Allow/Continue on the consent screen this time","If the app is unverified, complete OAuth verification or add the test user to the Google Cloud consent screen","If a workspace policy blocks the app, use an account outside that workspace or ask the admin to allowlist the client id"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match poll_device_code_tokens(client, id, secret, &device).await {\n    Ok(tokens) => tokens,\n    Err(e) if e.to_string() == \"User denied authorization\" => {\n        eprintln!(\"consent was denied; re-run the login and click Allow\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Tell the user exactly which consent screen to expect before starting the flow","Add the account as a test user while the OAuth app is in testing mode","Do not auto-retry a denial in a loop — it needs a human decision"],"tags":["oauth","device-code","gemini","consent","access-denied","rust"],"backgroundTag":"oauth-access-denied","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}