Skip to content

Recover from hung Google token refreshes#951

Draft
leits wants to merge 4 commits into
masterfrom
agent/fix-google-token-refresh-recovery
Draft

Recover from hung Google token refreshes#951
leits wants to merge 4 commits into
masterfrom
agent/fix-google-token-refresh-recovery

Conversation

@leits

@leits leits commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Status

DRAFT — CI is green; awaiting manual sleep/wake validation and maintainer review

Description

Fixes a Google Calendar reliability failure where one hung AppAuth token request can permanently block every later calendar refresh.

This is a smaller alternative to #950 and is limited to token refresh. Interactive sign-in is unchanged.

Root cause

OIDAuthState.performAction coalesces refresh callers in an internal _pendingActions queue. If its token-request callback never arrives, timing out only MeetingBar's await is insufficient: the next call joins the same stuck AppAuth queue.

Changes

  • Bound the AppAuth callback wait to 35 seconds with a small AsyncThrowingStream bridge.
  • Make callback, timeout, late-callback, and cancellation races terminate exactly once.
  • On timeout, detach the stuck OIDAuthState and reopen the state already persisted in Keychain. AppAuth does not archive _pendingActions, so the replacement can start a real request.
  • Retry token refresh once; after a second timeout, recover the state again and use the existing AuthError.refreshFailed.
  • Let the owning refresh task recover AppAuth state when cancellation interrupts an already-started operation.
  • Keep the cancelled task registered until it exits, preventing stale cleanup from clearing a newer refresh.
  • Prefer an AppAuth error over an old access token when both are returned.

Scope and trade-off

The PR changes three files and adds no transport class, no new shared auth error, no package-target wiring, and no downstream provider-health cases.

AppAuth does not expose the underlying token-request task, so a timed-out request may still finish later. Its old auth state has no delegates, however, so it cannot overwrite the current persisted state or keep future refreshes in its pending queue.

Automated validation

  • CI, app-hosted Xcode tests, SwiftPM tests, Lint, Security, localization validation, and coverage upload all pass.
  • App-hosted tests cover no callback, immediate callback, callback error, late callback followed by a clean call, and cancellation after the callback operation has started.
  • git diff --check
  • Scripts/validate_localizations.sh
  • Codecov exercises the timeout helper; 54 changed lines in the private AppAuth/Keychain integration path remain uncovered.

Manual verification before ready-for-review

  • Confirm an existing Google account still performs a normal calendar refresh.
  • Interrupt a forced token refresh across offline or sleep/wake, then restore connectivity; the next clean attempt should run without reconnecting the account.
  • Switch providers while refresh is pending; no result from the old provider should replace the new provider's state.
  • Sign out while refresh is pending; the Google session must stay signed out.

The local Linux workspace has no Swift/Xcode toolchain, so GitHub Actions is the build authority and the live Google sleep/wake case remains the manual gate.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Google authentication operations now have bounded async timeouts, explicit cancellation behavior, and late-callback isolation. Google calendar token refresh uses serialized retries, error classification, and persisted authentication-state recovery.

Changes

Google authentication refresh resilience

Layer / File(s) Summary
Timeout primitive and validation
MeetingBar/Calendar/Providers/Google/GoogleAuthTimeout.swift, MeetingBarTests/GoogleAuthTimeoutTests.swift
Callback-based authentication operations are wrapped with timeout and cancellation handling, while tests cover success, failure, timeout, cancellation, repeated calls, and late callbacks.
Token refresh retry and state recovery
MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift
Token refreshes use shared-task serialization, bounded attempts, timeout-aware execution, error classification, and persisted auth-state restoration after interruption or timeout.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GoogleCalendarEventStore
  participant GoogleAuthTimeout
  participant OIDAuthState
  GoogleCalendarEventStore->>GoogleAuthTimeout: Start bounded token refresh
  GoogleAuthTimeout->>OIDAuthState: Perform authentication action
  OIDAuthState-->>GoogleAuthTimeout: Return token or error
  GoogleAuthTimeout-->>GoogleCalendarEventStore: Return result, timeout, or cancellation
  GoogleCalendarEventStore->>GoogleCalendarEventStore: Recover auth state and retry when applicable
Loading

Poem

I’m a rabbit with a timeout bell,
Guarding tokens in their burrowed cell.
If callbacks hop late through the gate,
Retries restore the auth state.
Errors thump, then flow away—
Fresh green tokens greet the day.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding recovery for hung Google token refreshes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/fix-google-token-refresh-recovery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 28.00000% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.19%. Comparing base (88519a4) to head (9b61bc7).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ar/Providers/Google/GoogleCalendarEventStore.swift 0.00% 54 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #951      +/-   ##
==========================================
- Coverage   52.26%   52.19%   -0.08%     
==========================================
  Files          74       75       +1     
  Lines        8779     8835      +56     
  Branches     3367     3396      +29     
==========================================
+ Hits         4588     4611      +23     
- Misses       4053     4088      +35     
+ Partials      138      136       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

leits commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift`:
- Around line 324-325: Update the refresh task lifecycle around the Task created
in the Google calendar event store so deferred cleanup is associated with a
unique generation or UUID. When the task finishes, set refreshTask to nil only
if it still owns the recorded generation, preventing a canceled older task from
clearing a newer task; apply the same ownership check to the corresponding
cleanup at the other indicated location.

In `@MeetingBarLogicTests/GoogleAuthTimeoutTests.swift`:
- Line 16: Update both closure declarations in GoogleAuthTimeoutTests, including
the locations around the Result<String, Error> parameter and the second reported
occurrence, so each typed closure parameter appears on the line preceding its
opening brace. Preserve the existing parameter types and closure behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e4882365-4cf8-4396-b31a-95e6000a09a9

📥 Commits

Reviewing files that changed from the base of the PR and between 88519a4 and bdf98ae.

📒 Files selected for processing (9)
  • MeetingBar/Calendar/CalendarSync.swift
  • MeetingBar/Calendar/ProviderHealth.swift
  • MeetingBar/Calendar/Providers/Google/GoogleAuthTimeout.swift
  • MeetingBar/Calendar/Providers/Google/GoogleAuthTransport.swift
  • MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift
  • MeetingBar/Calendar/Providers/Google/GoogleCalendarPolicy.swift
  • MeetingBarLogicTests/GoogleAuthTimeoutTests.swift
  • MeetingBarLogicTests/GoogleCalendarPolicyTests.swift
  • Package.swift

Comment thread MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift
Comment thread MeetingBarLogicTests/GoogleAuthTimeoutTests.swift Outdated
@leits leits changed the title Fix hung Google token refresh with transport recovery Recover from hung Google token refreshes Jul 13, 2026

leits commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift (1)

314-405: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for validAccessToken's retry/recovery state machine
GoogleAuthTimeoutTests cover the timeout helper, but not the validAccessToken flow itself. Add cases for timeout→recover→retry→success/refreshFailed and cancellation→recover→rethrow, ideally through a stubbed OIDAuthState or a small seam around the refresh action.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift` around
lines 314 - 405, Add tests covering the validAccessToken refresh state machine:
verify timeout recovery restores auth state and retries to success, repeated
timeouts end in AuthError.refreshFailed, and cancellation recovers state before
rethrowing CancellationError. Exercise the flow through a stubbed OIDAuthState
or a focused seam around performTokenRefresh, and assert the relevant retry and
recovery outcomes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift`:
- Around line 314-405: Add tests covering the validAccessToken refresh state
machine: verify timeout recovery restores auth state and retries to success,
repeated timeouts end in AuthError.refreshFailed, and cancellation recovers
state before rethrowing CancellationError. Exercise the flow through a stubbed
OIDAuthState or a focused seam around performTokenRefresh, and assert the
relevant retry and recovery outcomes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2023f0f8-0204-4768-bed5-c87d99064881

📥 Commits

Reviewing files that changed from the base of the PR and between bdf98ae and 9b61bc7.

📒 Files selected for processing (3)
  • MeetingBar/Calendar/Providers/Google/GoogleAuthTimeout.swift
  • MeetingBar/Calendar/Providers/Google/GoogleCalendarEventStore.swift
  • MeetingBarTests/GoogleAuthTimeoutTests.swift

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant