fix(desktop): read the Chrome cookie key off the main thread on macOS#23163

open3
main
GitHub fork: r10/h9-1
opened8d agobyOonly21mil
View on GitHubTwo-way sync enabledPR last imported 1d ago

Problem

On macOS, importing cookies from Chrome (or Brave, Edge or another Chromium browser) reads that browser's "Safe Storage" keychain item with the synchronous Entry class from @napi-rs/keyring. The first import shows two macOS prompts for the "Chrome Safe Storage" item, and each asks for the login keychain password. The call runs in the Electron main process, so it holds the main thread until the user answers both prompts. Meanwhile every T3 Code window stops repainting, and accessibility requests to it (reading it, moving it) time out.

Change

readKeychainSecret in ChromiumKeys.ts now reads through AsyncEntry, which the same package runs on the libuv thread pool, inside Effect.tryPromise. The error mapping is unchanged. An undefined result (the async API's "no entry") counts as keychainItemMissing, like null and "" did before. The read stays untimed, as the existing comment explains, and stays in-process, so the prompt still names T3 Code. CursorKeychain.ts already reads its keychain entry the same way.

The test mock now exports both classes. Its Entry read throws, so the macOS cases fail if the code goes back to the synchronous read. One new case covers an undefined result.

Scope and approval

  • No issue filed. It's a focused fix of a main-thread block with a clear cause, in one function.
  • #21028 fixes the same kind of freeze for safeStorage at startup. It doesn't touch BrowserImport/, so the two don't overlap.
  • #22222 asked desktop fixes to come with proof from a real build. That proof is below.
  • Prior PRs: none for this fix. #10667 (merged) moved the keyring import into this function so it loads only for macOS imports; this keeps that.

Verification

Packaged builds on macOS 27.0.1 (arm64): unsigned DMGs from node scripts/build-desktop-artifact.ts --platform mac --target dmg --arch arm64 for main (67c0fc0cda) and for this branch. The main bundle has new Keyring.Entry(. The branch bundle has new Keyring.AsyncEntry( and no sync call.

Each run used a throwaway HOME with a fresh Chrome profile. Real Chrome wrote 3 test cookies, encrypted with the login keychain's Chrome Safe Storage key, so no real browsing data was read. T3 Code was not yet allowed to read that item, so both builds got the prompts. A test-only overlay injected through DevTools awaits one cheap IPC round trip to the main process every 100 ms (desktopBridge.getClientSettings) and shows how long ago the main process last replied. With the prompt open, a driver read the window's accessibility tree and moved the window 60 pt, three times. Then both prompts were answered with Allow.

Main, about 20 s into the prompt. The overlay still shows the frame painted just after the prompt opened:

Main build: overlay frozen behind the keychain prompt

This branch, about 9 s into the prompt. The overlay keeps counting replies:

Branch build: overlay live behind the keychain prompt

Probe results. On main every accessibility read hit its 10 s timeout and every move failed. One IPC round trip took 108,876 ms, the whole time the prompts were open. On the branch every read answered, every move was confirmed by WindowServer, and the slowest round trip was 56 ms:

Probe and heartbeat results, main then branch

25-second recordings of the prompt period from the same builds, cropped to the T3 window, password field covered.

Main (67c0fc0cda): the overlay stops at 19 replies while the prompt stays open.

Main 67c0fc0cda: 25-second recording with the keychain prompt open and the overlay frozen

This branch (fc5f74e4b7): the overlay advances from 21 to 263 replies while the prompt stays open.

Branch fc5f74e4b7: 25-second recording with the keychain prompt open and the overlay updating

After Allow, both builds import the 3 cookies. This is the branch:

Branch build: Imported 3 cookies

Focused tests: with main's ChromiumKeys.ts, the 3 macOS cases fail. On the branch, all 10 pass:

ChromiumKeys tests on main code and on the branch

Also on the branch: the BrowserImport folder's 9 test files (73 tests) pass, tsc-rs --noEmit passes in apps/desktop, and vp fmt --check and vp lint --report-unused-disable-directives pass on the two changed files. On macOS the full apps/desktop suite fails 2 DesktopLinuxUrlHandler.test.ts cases, which fail on main too: two Linux icon names differ only in case.

Not checked:

  • Brave, Edge, Vivaldi, Opera, Arc and Helium. They share this function but weren't run.
  • Deny on the branch. Deny was only clicked on main.
  • A signed or notarized build, and Intel Macs.
  • A beachball shot. No cursor hovered over the window; the probes and overlay stand in for it.

Production fix, tests, PR description and images written with Claude Opus 5.5; recording links and description update by GPT-6.1 Sol, both in T3 Code Nightly.

🤖 Generated with Claude Opus 5.5 and GPT-6.1 Sol in T3 Code Nightly

Checks

20 successful · 8 skipped
All checks passed
8648311

Added 2 commits

  • Merge main (a8bb769d65) into r10/h9-13c1dff0
  • fix(desktop): read the Chrome cookie key off the main thread on macOSfc5f74e
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This focused fix moves the macOS Chromium keychain prompt off the main thread and adds async-result coverage without changing the cookie-import flow. Because it changes production handling of keychain credentials used to decrypt browser cookies, the sensitive-data safety requirement calls for human review.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 3c1dff034eaa28709186778b2193b1afa8f1dc5b and 8648311d8396cbb55376a05521677fb937e8d7de.

External image Files selected for processing: 1
  • apps/desktop/src/preview/BrowserImport/ChromiumKeys.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Walkthrough

Chromium keychain reads now use the asynchronous keyring API. Falsy secrets produce keychainItemMissing. Tests cover successful reads, missing results, and rejected reads.

Changes: Chromium keychain reads

Layer / File(s)Summary
Asynchronous keychain read and result handling
apps/desktop/src/preview/BrowserImport/ChromiumKeys.ts, apps/desktop/src/preview/BrowserImport/ChromiumKeys.test.ts
readKeychainSecret uses Keyring.AsyncEntry(...).getPassword() through Effect.tryPromise. Falsy results produce keychainItemMissing. Tests use asynchronous mocks and cover resolved, undefined, and rejected reads.

Priority: External image Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related PRs

Suggested labels: size:S

Suggested reviewers: juliusmarminge

Merge Risk: External image Minimal · up to 86483

The asynchronous keychain read is supported and preserves the existing behavior needed for this workflow.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title names the change accurately: the Chrome cookie key read moves off the macOS main thread. It follows the conventional commit form and matches the ChromiumKeys.ts change, which switches readKe…
Description checkPassedThe description follows the template closely. It covers Problem (synchronous keychain read blocks the Electron main process during the two macOS prompts), Change (AsyncEntry via Effect.tryPromise, und…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the change accurately: the Chrome cookie key read moves off the macOS main thread. It follows the conventional commit form and matches the ChromiumKeys.ts change, which switches readKeychainSecret from the synchronous Entry to AsyncEntry inside Effect.tryPromise. It is specific and short, so a teammate scanning history would understand the primary change.

Full details: Description check

Explanation

The description follows the template closely. It covers Problem (synchronous keychain read blocks the Electron main process during the two macOS prompts), Change (AsyncEntry via Effect.tryPromise, undefined mapped to keychainItemMissing, test mock updated), Scope and approval (no issue filed, justification given for a focused fix, related PRs #21028, #22222, #10667 cited), and Verification (packaged macOS arm64 builds, probe and overlay results, 10 focused tests passing, untested areas listed). The template's Scope section asks for a linked triaged issue or maintainer-approved discussion, or an explanation of why the fix qualifies without one. The author gives that explanation, so the section is complete. The description also does not follow the template's header comments verbatim, but the required content is present.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Added 1 commit

  • chore(desktop): merge main into async cookie key fix8648311

Note

Written by gpt-6.1-sol on behalf of Maria

thanks @only21mil. kept your commits and the async keychain implementation, then merged main at 0a3b561bc090 into your branch. resolved the comment conflict using main's shorter explanation. the final diff changes three production lines and updates the existing test file: 21 insertions, 8 deletions across two files. no functionality was dropped.

verification at 8648311d8396cbb55376a05521677fb937e8d7de:

  • your regression tests against current main's implementation fail in all three macos cases; the seven linux/windows cases pass.
  • with the fix, all 78 browser-import tests pass across nine existing files on blacksmith.
  • vp run check:changed passes formatting, lint, both knip checks, desktop/scripts typechecks, and the 10 focused tests on blacksmith, compared against the actual main base.
  • github ci and coderabbit are green. two independent gpt-6.1-sol code reviews found no issues on this head. github reports no merge conflicts.

unverified: native macos keychain prompts, responsiveness during approval, denial, and a live cookie import at this merged head. this linux vm cannot exercise those paths, so no fresh native screenshots or recording are claimed. the original macos evidence remains in the description and covers the earlier implementation. web/mobile and provider behavior are unchanged.

the existing macroscope request for human approval of credential handling remains outstanding. no review findings remain to fix.

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

no changed runtime behavior could be verified on this linux boat vm. this is an unverifiable result, not a passing verification.

inspected the description, discussion, commit authors and full github diff at 8648311d8396cbb55376a05521677fb937e8d7de. the pr is open; none of its three pr commits is authored by juliusmarminge or t3dotgg. git.cafe counterpart: https://git.cafe/pingdotgg/t3code/pulls/23163.

uname -a confirms linux x86_64. the production change replaces the synchronous keyring read with AsyncEntry.getPassword() inside the macos-only readKeychainSecret path. resolveChromiumKeys reaches it only for request.platform === "darwin". the user path is desktop browser cookie import from a chromium browser, followed by the native macos safe storage approval prompt. a web client or codex turn on linux cannot reach that native path.

Not verified

main's freeze, the pr's responsiveness while the native prompt remains open, successful cookie decryption/import after allow, denial, missing keychain entries, and neighboring browser import behavior remain unverified. reproducing these requires a macos electron client with real chromium cookies and native keychain consent. no keychain was accessed and no password prompt was triggered.

because the changed behavior cannot run here, i did not create worktrees, install dependencies, start a dev server, run unrelated provider turns, or manufacture screenshots or recordings. no fresh runtime evidence is claimed; the older macos evidence in the description was not reproduced this turn. cafe reports 28 successful checks, zero failing and zero pending on this head; those do not prove the native behavior. no code, labels, branches or pr state changed.