Skip to content

fix(calendar/google): structured error logging and user-friendly messages for OAuth failures - #262

Merged
h4yfans merged 6 commits into
mainfrom
worktree-google-oauth-400-diagnostic
Apr 17, 2026
Merged

fix(calendar/google): structured error logging and user-friendly messages for OAuth failures#262
h4yfans merged 6 commits into
mainfrom
worktree-google-oauth-400-diagnostic

Conversation

@h4yfans

@h4yfans h4yfans commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

What

Add a two-layer error architecture for the Google Calendar OAuth flow: verbose structured logs in the terminal (status, error code, description) while the UI receives clear, actionable messages derived from RFC 6749 error codes.

Why

The OAuth token exchange was silently returning status 400 with no diagnostic information — the error body was swallowed entirely. This made it impossible to know whether the failure was invalid_client, invalid_grant, redirect_uri_mismatch, etc. Root cause turned out to be a misnamed env var (GOOGLE_CLIENT_SECRET vs GOOGLE_CALENDAR_CLIENT_SECRET), which was only discoverable after opening the error body.

How

  • oauth-errors.ts (new): maps RFC 6749 error codes and Google API status codes to user-friendly string constants (RECONNECT_NEEDED, MISCONFIGURED, ACCESS_DENIED, etc.)
  • oauth.ts: readOAuthErrorBody() helper reads + JSON-parses the response body safely; exchangeCodeForTokens logs {status, error, errorDescription, redirectUri, clientIdSuffix} and throws a friendly message; startup warns if GOOGLE_CALENDAR_CLIENT_SECRET is set (Desktop OAuth clients don't need it); scope validation after token exchange throws CALENDAR_SCOPE_NOT_GRANTED_MESSAGE with a log if Calendar scope was not granted
  • client.ts: throwCalendarApiFailure(response, operation): Promise<never> applied to all 5 API paths; refreshAccessTokenInner logs + maps errors the same way
  • oauth.test.ts: vi.hoisted() for logger mock (avoids TDZ with vi.mock() hoisting); new test asserts message excludes raw 400/invalid_grant and loggerMock.error captures structured technical fields

Type

  • fix

Test plan

  • Unit tests added/updated (oauth.test.ts — 400 error behavior + logger assertions)
  • Manual: connect Google Calendar → terminal shows {status, error, errorDescription}; UI shows "Your Google Calendar connection has expired. Please disconnect and connect again." instead of raw status

Checklist

  • Self-reviewed
  • No secrets committed
  • Files under 500 LOC
  • Immutable patterns used

h4yfans added 6 commits April 17, 2026 18:14
…messages for OAuth failures

- Extract oauth-errors.ts with userMessageForTokenEndpointError and userMessageForCalendarApiError
- Log full Google error body (status, error code, description) on token exchange failure
- Map RFC 6749 error codes to actionable user messages; never surface raw 400/invalid_grant in UI
- Warn on startup if GOOGLE_CALENDAR_CLIENT_SECRET is set (Desktop clients do not need it)
- Validate Calendar scope after token exchange; throw friendly message if scope not granted
- Apply same two-layer error pattern to Calendar API client (list/create/events/upsert/delete)
…on token 400

- Use vi.hoisted() for loggerMock to avoid TDZ with vi.mock() hoisting
- Assert thrown message excludes raw '400' and 'invalid_grant'
- Assert loggerMock.error called with status/error/errorDescription fields
@h4yfans
h4yfans merged commit 20f0cd0 into main Apr 17, 2026
2 checks passed
h4yfans added a commit that referenced this pull request May 6, 2026
…nostic

fix(calendar/google): structured error logging and user-friendly messages for OAuth failures
@h4yfans
h4yfans deleted the worktree-google-oauth-400-diagnostic branch May 6, 2026 16:36
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