fix(oauth): let Google refresh path run instead of forcing hourly reconsent#210
Merged
Merged
Conversation
…onsent Closes #209. Three coordinated changes so AgentCore Identity's refresh flow actually gets to run: * Add a TTL (default 3000s) to the in-process token cache. Without it, warmed entries were returned past the upstream 3600s lifetime, which meant AgentCore was never re-asked and never got the chance to refresh. * Stop writing the durable disconnect flag from the AfterToolCallEvent 401 retry path. A 401 just clears the cache; the BeforeToolCallEvent retry re-fetches from AgentCore (force_authentication=False) so refresh runs transparently. The disconnect flag is now reserved for the explicit Disconnect button in the settings page. * Add prompt=consent to Google's customParameters when the caller is forcing a fresh consent (force_authentication=True). Google only re-issues a refresh_token on subsequent grants if the consent screen is shown — without this, a Disconnect/Reconnect cycle leaves the vault with an access_token but no refresh_token, putting the user back in the hourly-reconsent loop on the next access-token expiry. Also drops the now-unused mark_disconnected wiring (parameter, type, method, base_agent closure) per the no-dead-code rule. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3 tasks
philmerrell
added a commit
that referenced
this pull request
May 4, 2026
Google's hourly-reconsent loop returns. The fix from #210 only sent `prompt=consent` when the durable disconnect flag was set, but the vault can lose its refresh token in other ways (testing-mode refresh-token expiry, user revocation at Google, or any path where AgentCore returns an authorize URL without our disconnect endpoint being involved). In those cases Google saw a previously-consented user, skipped the consent screen, and re-issued an access_token without a refresh_token — back into the loop on the next expiry. `initiate_consent` is by definition a "walk me through consent" path (the user clicked Connect/Reconnect after seeing they were not connected), so always pass `force_authentication=True` to `custom_parameters_for` here. This decouples the customParameters builder from the SDK's vault-bypass flag — `prompt=consent` always reaches Google on this path, while the silent-refresh fast path on `/status` and the agent-side gate is unaffected. Affects Google providers only; the vendor baseline returns {} for every other provider regardless of the flag. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.
Closes #209.
Summary
Users connected to Google connectors had to click Reconnect roughly every hour because the OAuth refresh path silently never ran. Three coordinated changes fix this.
A — drop the auto-disconnect on 401.
_handle_auth_failureused to write the durable disconnect flag whenever a tool returned a 401. The retry then called AgentCore withforce_authentication=True, which bypasses the vault entirely — including the still-valid refresh_token — and prompts for full re-consent. Now a 401 just clears the local cache and retries;_gatere-fetches from AgentCore (noforce_authentication) so the refresh runs transparently. The disconnect flag is now reserved for the explicit Disconnect button.B — TTL on the in-process cache.
oauth_token_cacheused to hold tokens forever. Once warmed, the cache returned the same access_token until eviction — so AgentCore was never re-asked, and never got the chance to refresh. The cache now has a default TTL of 3000s (under Google's 3600s access_token lifetime), so we proactively re-fetch and let AgentCore handle refresh before the upstream rejects the token.C —
prompt=consenton the explicit re-consent path.custom_parameters_for(...)now takes aforce_authenticationflag and addsprompt=consentfor Google when set. Google only re-issues a refresh_token on subsequent grants if the consent screen is shown — without this, a Disconnect/Reconnect cycle leaves the vault with an access_token but no refresh_token, putting the user right back in the hourly-reconsent loop. First-time consent and silent refreshes are unaffected.Also drops the now-dead
mark_disconnectedplumbing (parameter, type, method,base_agentclosure) per the no-dead-code convention.Test plan
test_oauth_token_cache.py,test_oauth_consent_hook.py,test_agentcore_identity.py)prompt=consentpropagation; silent refresh asserts it is not sent🤖 Generated with Claude Code