Skip to content

Self-heal expired Google cookies natively + alert on dead platforms#74

Merged
MaxGhenis merged 1 commit into
mainfrom
macos-cookie-refresh
Jul 3, 2026
Merged

Self-heal expired Google cookies natively + alert on dead platforms#74
MaxGhenis merged 1 commit into
mainfrom
macos-cookie-refresh

Conversation

@MaxGhenis

Copy link
Copy Markdown
Owner

Problem

Max's Google Messages (SMS/RCS) sync has repeatedly died silently. The session cookies Google issues rotate ~30 min; when the backend is offline long enough (sleep, travel), they expire and every token refresh returns HTTP 401. Today that parks the session as needs_repair and waits for a manual cookie-surgery re-pair — which is exactly the ordeal documented in docs/agent-runbook.md. In the latest incident the session was stale for 15 days with no notification.

The reconnect watchdog already had a self-heal path (OPENMESSAGE_COOKIE_REFRESH_SCRIPT), but it was never wired on the macOS app, and the only implementation was Linux/KWallet-specific.

Changes

1. Native in-process cookie refresh — internal/googlecookies

No external script, no Python, no re-pair. When cookies expire, the watchdog rewrites auth_data.cookies from the signed-in Chrome profile and reconnects.

  • Chrome Safe Storage key from the login keychain (macOS); AES-128-CBC / PBKDF2-HMAC-SHA1 decrypt; handles the Chrome 130+ SHA256(host) plaintext prefix; highest-priority host wins per cookie name.
  • Snapshots the cookie DB +WAL before reading, so freshly rotated cookies are visible while Chrome holds the live file.
  • refreshGoogleSessionCookies falls back to the native path when no script is set; canRefreshGoogleCookies() gates planGoogleReconnect so a recoverable session refreshes instead of parking.
  • darwin-only for now (Linux keeps the script path via build tags).

Verified end-to-end against the real Chrome profile: 52 cookies written, all 6 required session cookies present, phone_id/tachyon_token preserved, stale cookies dropped.

2. Health notifications — NotificationManager.swift

Alerts once, on the rising edge, when Google Messages flips to needs_repair or WhatsApp logs out; re-arms after recovery. Checked on both the status SSE event and the post-reconnect catch-up, so a flag flip isn't missed while the stream is down. This is the piece that would have surfaced the 15-day outage on day one.

3. scripts/refresh-google-session-cookies-macos.py

macOS reference implementation / OPENMESSAGE_COOKIE_REFRESH_SCRIPT override, mirroring the existing Linux script (keychain instead of KWallet, openssl for AES so it ships dependency-free).

Testing

  • internal/googlecookies unit tests: decrypt round-trip (incl. host-hash prefix), non-v10 passthrough, session-field preservation, required-cookie enforcement, host-priority dedup.
  • serve_test.go updated for the new signature; native path made deterministic via OPENMESSAGE_CHROME_PROFILE.
  • go build / go vet / go test ./... green; swift build green.

Notes

🤖 Generated with Claude Code

Two changes that together stop the recurring "SMS silently stopped
syncing N days ago" failure without any manual cookie surgery.

1. Native in-process Google cookie refresh (internal/googlecookies).
   When the Google Messages session's cookies expire (HTTP 401 on token
   refresh), the reconnect watchdog can now rewrite auth_data.cookies
   from the user's signed-in Chrome profile and reconnect — no external
   script, no Python, no re-pairing. Previously this only worked if the
   operator had wired OPENMESSAGE_COOKIE_REFRESH_SCRIPT (never set on the
   macOS app), so an expired session just parked as needs_repair.

   - Reads the Chrome Safe Storage key from the login keychain (macOS),
     derives the AES-128-CBC key (PBKDF2-HMAC-SHA1), decrypts cookies,
     handles the Chrome 130+ SHA256(host) plaintext prefix, and picks the
     highest-priority host per cookie name.
   - Snapshots the cookie DB (+WAL) before reading so freshly rotated
     cookies are visible even while Chrome holds the live file.
   - serve.go's refreshGoogleSessionCookies now falls back to this native
     path when no script is configured; canRefreshGoogleCookies() gates
     planGoogleReconnect so a recoverable session refreshes instead of
     parking. darwin-only for now (Linux keeps the script path).
   - Verified end-to-end against the real Chrome profile: 52 cookies
     written, all 6 required session cookies present, other session
     fields preserved.

2. Health notifications (NotificationManager.swift). The app now alerts
   once, on the rising edge, when Google Messages flips to needs_repair
   or WhatsApp logs out — re-arming after recovery. The motivating
   failure was Google going stale for 15 days with no signal beyond a
   subtle in-app badge. Checked on both the status SSE event and the
   post-reconnect catch-up so a flag flip isn't missed while the stream
   is down.

Also ships scripts/refresh-google-session-cookies-macos.py as the macOS
reference implementation / OPENMESSAGE_COOKIE_REFRESH_SCRIPT override,
mirroring the existing Linux script.

Tests: new googlecookies unit tests (decrypt round-trip incl. host-hash
prefix, session-field preservation, required-cookie enforcement, host
priority); serve_test updated for the new signature. go build/vet/test
green; swift build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis merged commit 78c4b5f into main Jul 3, 2026
5 checks passed
@MaxGhenis MaxGhenis deleted the macos-cookie-refresh branch July 3, 2026 13:42
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