fix(init): re-auth instead of hard-failing when the stored token is expired#1
Open
piekstra wants to merge 1 commit into
Open
fix(init): re-auth instead of hard-failing when the stored token is expired#1piekstra wants to merge 1 commit into
piekstra wants to merge 1 commit into
Conversation
…xpired
An expired/revoked refresh token fails during token refresh inside the
oauth2 transport: the token endpoint returns HTTP 400 invalid_grant,
surfaced as *oauth2.RetrieveError wrapped in *url.Error — the request
never reaches the API, so no googleapi 401 and no "401" substring
exists for isAuthError to match. tryExistingToken then propagated the
error and init aborted:
Error: getting profile: Get "https://gmail.googleapis.com/...":
oauth2: "invalid_grant" "Token has been expired or revoked."
forcing users to manually delete the keyring entry before init would
run — the exact remediation init exists to provide. Testing-mode OAuth
apps hit this every 7 days.
isAuthError now recognizes *oauth2.RetrieveError with ErrorCode
invalid_grant (and the corresponding strings in wrapped/legacy shapes,
which only the token endpoint can produce), routing verification
failures into the existing re-auth confirm + fresh OAuth flow.
Also: under --auth-code-stdin the re-auth confirmation prompt is
skipped (with a notice) — the two-phase install has no TTY and stdin
is reserved for the auth code, so the huh prompt could only fail.
piekstra-dev
approved these changes
Jul 24, 2026
piekstra-dev
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: 374e73cfca19
Profile: reviewer - Posting as: piekstra-dev
Summary
| Reviewer | Findings |
|---|---|
| go:implementation-tests | 0 |
| policies:conventions | 0 |
Reviewer Coverage
| Reviewer | Status | Inspected | Skipped | Constraints |
|---|---|---|---|---|
| go:implementation-tests | complete_broad | initcmd/init.go, initcmd/init_test.go | unavailable | git commands were unavailable in this sandbox, so the exact diff hunks could not be inspected; review is based on the full head-branch contents of the two assigned files plus the PR intent description. |
| policies:conventions | complete_broad | initcmd/init.go, initcmd/init_test.go | unavailable | Shared CLI standards at open-cli-collective/cli-common/docs and ../.github were not present in the review context, so no comparison against those canonical docs was possible. |
0 PR discussion threads considered. 0 summarized; 0 resolved.
Completed in 2m 32s | $1.94 | claude-sonnet-5 | cr 0.10.268
| Field | Value |
|---|---|
| Model | claude-sonnet-5 |
| Reviewers | go:implementation-tests, policies:conventions |
| Engine | claude_cli · claude-sonnet-5 |
| Reviewed by | cr · piekstra-dev |
| Duration | 2m 32s wall · 3m 24s compute |
| Cost | $1.94 |
| Tokens | 74 in / 13.8k out |
Per-workstream usage
| Workstream | Model | In | Out | Cache read | Cache create | Cost | Duration |
|---|---|---|---|---|---|---|---|
| orchestrator-selection | claude-sonnet-5 | 6 | 1.9k | 27.9k | 31.8k | $0.23 | 23s |
| go:implementation-tests | claude-sonnet-5 | 32 | 7.0k | 640.7k | 87.2k | $0.82 | 1m 50s |
| policies:conventions | claude-sonnet-5 | 30 | 4.5k | 526.0k | 75.0k | $0.68 | 1m 01s |
| orchestrator-rollup | claude-sonnet-5 | 6 | 429 | 41.8k | 32.1k | $0.21 | 9s |
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
gro init --credentials-file <json>hard-fails when the stored OAuth token is expired or revoked, instead of offering re-auth:The only workaround is manually deleting the keyring entry (
security delete-generic-password -s google-readonly -a "default/oauth_token") — the exact remediationinitexists to provide. Testing-mode OAuth apps expire refresh tokens every ~7 days, so this bites constantly.Cause
An expired refresh token fails during token refresh inside the oauth2 transport: the token endpoint returns HTTP 400
invalid_grant, surfaced as*oauth2.RetrieveErrorwrapped in*url.Error. The request never reaches the Gmail API, so there is nogoogleapi.Error401 and no"401"substring —isAuthErrorreturned false, andtryExistingTokenpropagated the error as fatal instead of falling through to the re-auth flow (which already exists and handles googleapi 401s fine).Fix
isAuthErrornow recognizes*oauth2.RetrieveErrorwithErrorCode == "invalid_grant"viaerrors.As(works through theurl.Error/fmt.Errorfwrapping), plus the corresponding strings in wrapped/legacy error shapes — those strings only ever come from the OAuth token endpoint, so they're definitive; a bare401still requires corroboration, and genuine network failures are unaffected.--auth-code-stdin(two-phase install) the re-auth confirmation prompt is now skipped with a notice: there's no TTY and stdin is reserved for the auth code, so thehuhprompt could only ever fail with "could not open a new TTY".Behavior for a valid existing token is unchanged ("Already authenticated as …" short-circuit), as are the stale-scope (#107) paths.
Testing
TestIsAuthErrortable entries: bare and production-wrappedRetrieveError(invalid_grant→ true, other codes → false), no-401invalid_grantstring → true.TestRunWithExpiredRefreshTokenPromptsReauth: regression test — the exact production error shape now routes into confirm → delete → fresh OAuth flow.TestRunWith_AuthCodeStdinExpiredTokenSkipsReauthPrompt: two-phase install with an expired token completes without any interactive prompt.go build ./...,go vet ./...,go test ./...,golangci-lint runall clean.Note for consumers:
google-readonly(gro) andgoogle-readwrite(grw) both get this via the sharedinitcmdpackage — each needs a dependency bump after this merges.https://claude.ai/code/session_018kd6nduhCb2p3RzVvbPnMF