Skip to content

fix(calendar): log and show why connecting a calendar failed - #669

Merged
Valtora merged 1 commit into
mainfrom
fix/calendar-connect-errors
Oct 23, 2026
Merged

Valtora merged 1 commit into
mainfrom
fix/calendar-connect-errors

Conversation

@Valtora

@Valtora Valtora commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Pull Request

Description

Fixes #668.

Why nothing was shown. The callback's 303 went to /settings?tab=account&calendar=error&provider=google. Since the settings reorganisation (#154), the settings index turns ?tab=account into /settings/profile and drops every other query parameter, and the Calendars card lives on Integrations. No callback outcome has reached the page since then, success included. The redirect now points at /settings/integrations?calendar=<status>&provider=<provider>.

Why nothing was logged. The calendar list's HTTPStatusError escaped handle_callback, and the endpoint's catch-all redirected without a log line. The three provider steps after consent (token exchange, account lookup, calendar list) now run in _read_connecting_account, which raises CalendarConnectFailed with the step, the provider's error code and a described failure. The endpoint logs Connecting a google calendar failed at the calendar list step: HTTP 403 <reason>: <message> and adds reason=<code> to the redirect. Nothing is stored until all three steps succeed, so a 403 at the calendar list still leaves no calendar_connections row. The Microsoft callback shared the gap and gets the same handling.

Also logged now: an authorisation error the provider sends back in the query string (the OAuth error and any AADSTS number, never the description), a configuration or expired-session failure, any other callback or start failure (by type), and the reason for a failed initial or periodic sync.

What the user sees. A failed connection shows an error notice at the top of the Calendars card, kept until dismissed or the next Connect. It has the title "Could not connect Google Calendar" (or Outlook Calendar), a plain-words explanation for known codes, a Google Workspace or Microsoft 365 hint where one applies, and "Reason given by Google: ". A success or a cancelled sign-in is still a toast.

Shared helpers. _truncate_error, _google_error_reason, _provider_error_fields and _describe_push_failure (renamed _describe_provider_failure) move from calendar_push_service.py to calendar_service/provider_errors.py, and the push service imports them. Push behaviour is unchanged, except that an email address a provider echoes back is now masked in the log and last_error.

docs/CALENDAR.md gains "Connecting Fails After Sign-In", with the common reasons and where the log line appears.

Decision record

  • Fix the redirect target, not the legacy-tab handler. The callback is the only sender of these parameters, and the frontend and API ship together. Making SettingsIndex carry query strings through legacy redirects would also work, but it changes every old ?tab= link for no current need.
  • Only the provider's code travels in the URL. The free-text message stays in the log. The code must match [A-Za-z][A-Za-z0-9_.-]{0,63} in the API and again in the page, so a crafted link cannot put arbitrary text on the settings page. Google's numeric error.code repeats the HTTP status, so the log keeps HTTP 403 with errors[].reason beside it.
  • A notice in the card for failures, a toast for the rest. A toast goes after five seconds, and these messages carry a hint someone may need to pass to an administrator. The configuration and tenant errors moved into the notice as well.
  • A 403 on the calendar list fails the connection. Saving the account would leave one that cannot sync, shown as "Needs attention" with no way forward but Disconnect.
  • Catch-all boundaries log the exception type only. A database error's message carries its statement parameters, which can hold calendar names and event text. Provider and network errors, and #667's refused-grant error, are described in full, with provider text capped at 200 characters and addresses masked.
  • Codes with their own wording: Google's notACalendarUser, accessNotConfigured, insufficientPermissions, ACCESS_TOKEN_SCOPE_INSUFFICIENT, forbidden, PERMISSION_DENIED and admin_policy_enforced; Graph's ErrorAccessDenied and MailboxNotEnabledForRESTAPI; the token endpoint's invalid_client, unauthorized_client and invalid_grant. Any other code reads "Google refused the connection." with the code shown as given.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behaviour)
  • Documentation update

Checks run

  • Python: scripts/check.py gates (lint, format, blindexcept, heldpins, typecheck, docs, alembic) and the full pytest suite with Postgres, 3692 passed. Host side: whitespace, file size, docs validator, git diff --check.
  • Frontend: npm run lint, npm run check:contrast (1188 pairings), npm run test (1765 passed), npm run build.
  • Site: cd site && npm ci && npm run build
  • Docs only: python3 scripts/validate_docs.py

Two API tests drive the Google and the Microsoft callback through a stub provider that accepts the code and the account lookup, then answers the calendar list with 403. They check the redirect's reason, the log line, that no token, code, state or address reaches the log, and that no calendar_connections row exists. Further tests cover the authorisation-error path, a crafted error value, the shared helpers and the card's notice. All of them fail on main.

Migration impact

  • No database migration.
  • Adds an Alembic migration: one checked-in head, no committed revision deleted or renamed, upgrade and downgrade behaviour described above, and python3 scripts/check_migrations.py run against a throwaway Postgres.

Documentation impact

  • No documentation change required.
  • Updated the guide that owns each changed behaviour, setup, deployment or support topic (.github/CONTRIBUTING.md, "Documentation Ownership"): docs/CALENDAR.md, Connecting a Calendar and a new Connecting Fails After Sign-In section.

Security impact

  • No security-sensitive change. The redirect is still a fixed relative path, and the added reason is shape-checked on both sides. Log text comes from the response body's error fields only.
  • Touches auth, tokens, encryption, capture ownership or exposure; docs/SECURITY.md boundaries preserved and updated where behaviour changed.

Manual verification

None by hand beyond the tests. After the live rebuild, connecting the Workspace account from #668 again should show the notice in the Calendars card and log the reason Google gave.

The OAuth callback redirected to /settings?tab=account, whose legacy
redirect drops the query string, so no outcome reached the Calendars
card. It now lands on /settings/integrations.

A failure at the token exchange, account lookup or calendar list step
is logged with the step, the HTTP status and the provider's error code
and message, and the code travels to the card as `reason`. The card
shows the failure inline, with hints for the common Google Workspace
and Microsoft 365 causes. The provider-error helpers move from the push
service into calendar_service so both paths share them.

Fixes: #668
@Valtora
Valtora merged commit 80c5ecd into main Oct 23, 2026
21 checks passed
@Valtora
Valtora deleted the fix/calendar-connect-errors branch October 23, 2026 19:47
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.

Connecting a calendar fails silently when the provider refuses the calendar list

1 participant