{"record":{"id":"f8b6a653103ee075","repo":"google-gemini/gemini-cli","slug":"authentication-cancelled-by-user","errorCode":null,"errorMessage":"Authentication cancelled by user.","messagePattern":"Authentication cancelled by user\\.","errorType":"exception","errorClass":"FatalCancellationError","httpStatus":null,"severity":"warning","filePath":"packages/core/src/agents/auth-provider/oauth2-provider.ts","lineNumber":249,"sourceCode":"    };\n\n    const pkceParams = generatePKCEParams();\n    const preferredPort = getPortFromUrl(flowConfig.redirectUri);\n    const callbackServer = startCallbackServer(pkceParams.state, preferredPort);\n    const redirectPort = await callbackServer.port;\n\n    const authUrl = buildAuthorizationUrl(\n      flowConfig,\n      pkceParams,\n      redirectPort,\n      /* resource= */ undefined, // No MCP resource parameter for A2A.\n    );\n\n    const consent = await getConsentForOauth(\n      `Authentication required for A2A agent: '${this.agentName}'.`,\n    );\n    if (!consent) {\n      throw new FatalCancellationError('Authentication cancelled by user.');\n    }\n\n    coreEvents.emitFeedback(\n      'info',\n      `→ Opening your browser for OAuth sign-in...\n\n` +\n        `If the browser does not open, copy and paste this URL into your browser:\n` +\n        `${authUrl}\n\n` +\n        `💡 TIP: Triple-click to select the entire URL, then copy and paste it into your browser.\n` +\n        `⚠️  Make sure to copy the COMPLETE URL - it may wrap across multiple lines.`,\n    );\n\n    try {","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/core/src/agents/auth-provider/oauth2-provider.ts#L231-L267","documentation":"Before opening the browser for the OAuth flow, getConsentForOauth prompts the user; if they decline, the provider throws FatalCancellationError('Authentication cancelled by user.'). This is an intentional user action, not a system failure. FatalCancellationError signals that retrying the same flow without a different user decision will not help, so callers should treat it as terminal for this attempt.","triggerScenarios":"The user answered 'no' to the consent prompt; the prompt timed out and defaulted to decline; a non-interactive context where consent could not be obtained.","commonSituations":"User does not want to grant access to the agent; accidental decline; automation/sandbox where the prompt cannot render; the user cancelled to switch agents.","solutions":["Catch FatalCancellationError specifically and surface a clean 'cancelled' message rather than an error.","Re-invoke the flow only when the user explicitly requests it again.","For non-interactive contexts, pre-seed a stored token to skip the consent prompt.","Do not auto-retry on this error; it is not transient."],"exampleFix":"// before\nconst token = await provider.getToken();\n\n// after\ntry {\n  const token = await provider.getToken();\n} catch (e) {\n  if (e instanceof FatalCancellationError) {\n    console.log('User cancelled authentication.');\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isAuthCancelled(e: unknown): boolean {\n  return e instanceof FatalCancellationError\n    && /cancelled by user/i.test(e.message);\n}","tryCatchPattern":"try {\n  return await provider.getToken();\n} catch (e) {\n  if (e instanceof FatalCancellationError) {\n    // user declined - do not retry automatically\n    return { cancelled: true } as const;\n  }\n  throw e;\n}","preventionTips":["Catch FatalCancellationError separately and surface a clean cancellation.","Never auto-retry user cancellations; they are intentional and terminal.","In non-interactive contexts, pre-seed stored tokens to avoid the consent prompt."],"tags":["auth","oauth2","cancellation","user-action"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}