Skip to content

fix: sync works on Chrome-extension deployments (service worker path)#27

Draft
edwinhu wants to merge 1 commit into
mainfrom
fix/account-sync-chrome-extension
Draft

fix: sync works on Chrome-extension deployments (service worker path)#27
edwinhu wants to merge 1 commit into
mainfrom
fix/account-sync-chrome-extension

Conversation

@edwinhu

@edwinhu edwinhu commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Problem

superhuman sync (and forced sync) failed on Chrome-extension deployments (Linux/omarchy, where there is no Superhuman.app) with:

<account>: background_page unreachable — is Superhuman.app running with --remote-debugging-port?

Root cause: account-sync.ts connected to the per-account background sync engine only via the Electron background_page.html iframe path (connectToBackgroundPage). On this box Superhuman runs as the MV3 Chrome extension, which exposes a service_worker (.../background/background_page.js) — not an Electron page at background_page.html. So the matcher never found a target.

Meanwhile account auth already worked here, because it was ported to the extension service worker (connectToSuperhumanChrome / backgrounds[email]._accountBackground). sync --check only appeared to work because it silently fell back to on-disk enumeration and never actually reached CDP.

Fix

Port account-sync.ts onto a backend-agnostic SyncSession abstraction, mirroring the auth path:

  • extensionSession — reaches the engine as backgrounds[email]._accountBackground.di.get("sync") in the single service-worker context.
  • electronSession — the existing window.background per-iframe path, preserved for the macOS/Windows desktop app.
  • connectSyncSession — tries the extension first (common shape on Linux), falls back to Electron.

The freshness short-circuit, sync.start() trigger, forceSyncBackend escalation, and _lastRunEnded poll-until-advance logic are unchanged — only the eval context / account-selection is abstracted. cmdSync error messages are now backend-agnostic.

Verification

  • Live against the extension on port 9222: forced sync of both linked accounts returns synced (poll cycle observed via _lastRunEnded advancing); sync --check enumerates via the extension instead of the on-disk fallback.
  • bun test src/__tests__/account-sync.test.ts → 12/12 pass (ported to the SyncSession seam).
  • Full suite: 435 pass / 0 fail. tsc --noEmit clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_017G6xd9S8uc7WrQcCcMr59Q

`sync`/`account-sync.ts` connected to the account's background sync engine
only via the Electron `background_page.html` iframe path
(connectToBackgroundPage). On Chrome-extension deployments (Linux/omarchy,
where there is no Superhuman.app), that target doesn't exist — only the MV3
extension's service worker does — so every `sync` (and forced sync) failed
with "background_page unreachable", even though `account auth` already worked
via the extension service worker. `sync --check` only appeared to work
because it silently fell back to on-disk enumeration and never reached CDP.

Port account-sync.ts onto a backend-agnostic `SyncSession` abstraction with
two implementations, mirroring how the auth path was already ported:
  - extensionSession: reaches the per-account engine as
    `backgrounds[email]._accountBackground.di.get("sync")` in the single
    service-worker context (via connectToSuperhumanChrome).
  - electronSession: the existing `window.background` per-iframe path
    (connectToBackgroundPage), preserved for the macOS/Windows desktop app.

connectSyncSession tries the extension first (the common shape on Linux) and
falls back to Electron. The freshness short-circuit, sync.start() trigger,
forceSyncBackend escalation, and _lastRunEnded poll-until-advance logic are
unchanged — only the eval context/account-selection is abstracted. Verified
live against the extension on port 9222: forced sync of both linked accounts
returns "synced" (poll cycle observed), and `sync --check` enumerates via the
extension instead of the on-disk fallback.

Tests ported to the SyncSession seam (12/12 pass); full suite 435 pass / 0
fail; tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017G6xd9S8uc7WrQcCcMr59Q
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