fix: Google OAuth — correct scope + device code flow for SSH - #56
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes Google (Gemini) OAuth login failures caused by an invalid OAuth scope by switching to a valid Google scope that works with the Gemini API.
Changes:
- Update Google OAuth scope from
generative-languagetocloud-platform. - Update the URL-encoding unit test to match the new scope string.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The scope 'generative-language' is not a valid Google OAuth scope and causes a 400 invalid_scope error during login. The Gemini API's generateContent endpoint has no required scopes in Google's discovery document — the correct scope is 'cloud-platform', which is what Google's own Gemini CLI uses.
Auto-detect headless/SSH environment and use Google's device code flow instead of loopback redirect. This lets users authenticate over SSH by visiting a URL and entering a code on any device. Implementation: - New TV/Limited Input OAuth client for device code flow - is_headless() detects SSH_CONNECTION, SSH_TTY, missing DISPLAY - Device code polling with backoff per Google spec - client_hint field on OAuthCredentials so refresh uses correct client - Loopback flow uses cloud-platform scope (browser flow) - Device flow uses openid+email scope (Gemini API has no scope requirements) Both flows store credentials identically — existing loopback auth and API key auth continue to work unchanged.
The Gemini API's generateContent has no scope requirements (confirmed via Google's discovery doc). Using cloud-platform was massively over-privileged — it grants full read/write to all Google Cloud resources. Both flows now use 'openid email' (minimum for auth).
assapir
force-pushed
the
fix/google-oauth-scope
branch
from
March 2, 2026 13:21
2ddda32 to
a3452f3
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Make client_hint doc comment provider-agnostic (OAuthCredentials is shared) - Use neutral example URL in urlencoded test (no stale scope reference)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Google OAuth login fails with:
Additionally, the loopback redirect flow doesn't work over SSH (browser can't reach
127.0.0.1on the remote machine).Root Cause
generative-languageis not a valid Google OAuth scope. It doesn't appear in any Google API discovery document.cloud-platform(used by Google's Gemini CLI) grants full read/write to ALL Google Cloud resources. The Gemini API'sgenerateContenthas no scope requirements per the discovery doc — any valid OAuth token works.Fix
Minimal OAuth scope
Changed scope to
openid emailfor both flows — minimum needed for a valid OAuth token. No over-privileged access.Device code flow for SSH
Added a second OAuth client (TV/Limited Input type) that supports device code flow:
SSH_CONNECTION,SSH_TTY, missingDISPLAY)Token refresh
Added
client_hintfield toOAuthCredentials(backward-compatible viaserde(default)) so refresh uses the correct OAuth client for each flow.Auth flows
localhost, TCP listener catches codeGEMINI_API_KEYenv var (unchanged)Files changed
src/auth/google_oauth.rs— scope fix, device code flow, per-client refreshsrc/auth/oauth.rs—client_hintfield onOAuthCredentialssrc/auth/storage.rs— passclient_hintto Google refreshsrc/provider.rs— auto-detect headless, dispatch to correct flowtests/auth_test.rs— updated for new field