[BUG] Claude Desktop macOS: ~107s startup beachball — ReadMacOSKeychainCertificates blocks main thread
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Desktop
What's Wrong?
So the main/UI thread synchronously enumerates the login keychain and runs SecTrustEvaluateWithError (revocation-enabled) on every cert via blocking XPC to trustd.
Root cause on my machine
- Login keychain had 1,359 certificates (junk
CN=Test Certificateentries leaked by a test suite over months). - 1,359 certs × ~80ms per synchronous trust evaluation ≈ 108s, matching the observed 107s stall almost exactly.
- Removing the excess certs → startup dropped to ~instant. Confirmed by the
event-loop-stallline disappearing frommain.log.
Important: the CLI workaround does not fix the desktop app
CLAUDE_CODE_CERT_STORE=bundled(the CLI workaround from #53660) has no effect on the desktop app — the Electron/Node system-store enumeration isn't gated by it — so GUI users have no env-var mitigation, only manual keychain cleanup.
Requests
- Don't perform keychain enumeration / trust evaluation on the UI thread — move it off-main and make it async.
- Use a non-revocation policy (
SecPolicyCreateBasicX509) or read anchors directly viaSecTrustSettingsCopyCertificates, instead ofSecTrustEvaluateWithError, since this is collecting trust anchors, not validating chains. - Honor
CLAUDE_CODE_CERT_STORE=bundledin the desktop app too. - This scales badly: the original report is ~10s for 18 certs; here it's ~107s for ~1,359. A bloated (but otherwise legitimate) keychain shouldn't be able to freeze the app for nearly two minutes.
What Should Happen?
Claude Desktop application should not beachball on a mac for almost 2 minutes
Error Messages/Logs
### Error messages / logs
**1. `~/Library/Logs/Claude/main.log` — the freeze (a ~107s gap, then the stall is reported):**
22:08:13 [info] [remote-tools-device] connecting DO bridge with: get_device_info, device_list_dir, ... Filesystem__list_allowed_directories
<< ~107 seconds, UI fully frozen (beachball) >>
22:10:00 [warn] [event-loop-stall] main process blocked for 107153ms [likely sleep/wake] (total 0, cumulative 0ms, rss 409MB)
22:10:00 [warn] [my-access] fetch failed { error: DOMException [TimeoutError]: The operation was aborted due to timeout }
22:10:00 [warn] [buddy-ble] mainView not ready: Timed out waiting for mainView to become ready
22:10:00 [error] [RemotePluginManager] Sync failed: This operation was aborted (AbortError)
22:10:00 [info] [EventLogging] Flushing 38 events
The `[likely sleep/wake]` tag is a misattribution — the machine was awake and actively launching. The surrounding timeouts (`my-access`, `RemotePluginManager`) are collateral: their timers couldn't fire while the loop was blocked, so they all abort the instant it unblocks. The line that names the actual blocker is the `sample` below, not these.
**2. `sample` of the main process captured during the freeze — 14973/14973 samples on the same stack:**
<details>
<summary>Main-thread stack (com.apple.main-thread / CrBrowserMain)</summary>
Process: Claude [68256]
Version: 1.19367.0
Code Type: ARM64
OS Version: macOS 26.5.1 (25F80)
Analysis Tool: /usr/bin/sample (1ms interval, 14973 samples)
14973 CrBrowserMain
... Electron / Node ...
node::crypto::GetSystemCACertificates
node::crypto::GetOpenSSLSystemCertificates
node::crypto::ReadMacOSKeychainCertificates
node::crypto::IsCertificateTrustedForPolicy
node::crypto::IsCertificateTrustValid
SecTrustEvaluateWithError (Security)
SecTrustEvaluateInternal
SecTrustEvaluateIfNecessary
_dispatch_lane_barrier_sync_invoke_and_complete (libdispatch)
securityd_send_sync_and_do (Security)
security_fw_send_message_with_reply_sync_inner
xpc_connection_send_message_with_reply_sync (libxpc)
dispatch_mach_send_with_result_and_wait_for_reply
mach_msg -> mach_msg2_trap [blocked, waiting on trustd]
</details>
100% of the freeze is the main/UI thread synchronously enumerating the login keychain and calling `SecTrustEvaluateWithError` (revocation-enabled) on each certificate via **blocking XPC to `trustd`**.
**3. Root cause confirmed — bloated login keychain:**
$ security find-certificate -a ~/Library/Keychains/login.keychain-db \
| grep -o '"labl"<blob>="[^"]*"' | sort | uniq -c | sort -rn | head
1359 "labl"<blob>="Test Certificate"
2 "labl"<blob>="localhost"
1,359 certs × ~80ms per synchronous trust evaluation ≈ **108s**, matching the observed 107,153ms stall almost exactly.
**4. Fix / confirmation:**
# after deleting the 1,359 certs by SHA-1 hash:
$ security find-certificate -a ~/Library/Keychains/login.keychain-db \
| grep -c '"labl"<blob>="Test Certificate"'
0
Startup is now near-instant and `main.log` no longer contains any `[event-loop-stall] ... blocked` line.
**Note:** `CLAUDE_CODE_CERT_STORE=bundled` (tried via direct launch and `launchctl setenv`) had **no effect** on the desktop app — the Electron/Node path enumerates the system store via `--use-system-ca` regardless, so GUI users have no env-var mitigation, only manual keychain cleanup.
Steps to Reproduce
Steps to reproduce
The freeze scales with the number of certificates in the login keychain, because each one is trust-evaluated synchronously on the UI thread at startup. You can reproduce it by seeding the keychain and cold-launching.
⚠️ Back up your keychain first — this adds throwaway certs to your login keychain: cp ~/Library/Keychains/login.keychain-db ~/Desktop/login.keychain-db.bak
1. Record a baseline. Quit Claude Desktop fully, launch it, and note it starts quickly. Confirm no stall is logged:
tail -n 80 ~/Library/Logs/Claude/main.log | grep -i "event-loop-stall" # expect: no output
2. Seed N distinct certificates into the login keychain (each openssl run makes a unique cert, so the keychain doesn't dedupe them):
for i in $(seq 1 500); do
openssl req -x509 -newkey rsa:2048 -nodes -keyout /tmp/k.pem -out /tmp/c.pem \
-days 1 -subj "/CN=Repro Test Certificate" >/dev/null 2>&1
security add-certificates -k ~/Library/Keychains/login.keychain-db /tmp/c.pem >/dev/null 2>&1
done
# confirm the count
security find-certificate -a ~/Library/Keychains/login.keychain-db \
| grep -c '"labl"<blob>="Repro Test Certificate"'
3. Cold-launch and measure. Quit Claude Desktop fully (Cmd+Q, confirm it's gone from Activity Monitor), relaunch, and observe the beachball. Then:
tail -n 80 ~/Library/Logs/Claude/main.log | grep -i "event-loop-stall"
You'll see a main process blocked for <N>ms line whose duration grows roughly linearly with the cert count.
4. (Optional) Capture the stack to confirm it's the same code path:
open -a Claude
sleep 15
sample "$(pgrep -xn Claude)" 20 -file ~/Desktop/claude-hang.txt
The main thread will sit in ReadMacOSKeychainCertificates -> SecTrustEvaluateWithError -> xpc ... -> mach_msg.
Observed scaling (per-cert cost is environment-dependent — corporate NetworkExtension filters make it far worse):
| Certs in login keychain | Startup freeze |
|---|---|
| ~0 (baseline) | none / instant |
| 18 (corporate, per #53660) | ~10s (~530ms/cert) |
| 1,359 (this report, plain home Mac) | ~107s (~80ms/cert) |
Cleanup — remove the seeded certs (-c name-delete fails on duplicate names, so delete by SHA-1 hash):
security find-certificate -a -c "Repro Test Certificate" -Z ~/Library/Keychains/login.keychain-db \
| awk '/SHA-1 hash:/ {print $3}' \
| while read -r h; do
security delete-certificate -Z "$h" ~/Library/Keychains/login.keychain-db >/dev/null 2>&1
done
# verify back to 0
security find-certificate -a ~/Library/Keychains/login.keychain-db \
| grep -c '"labl"<blob>="Repro Test Certificate"'
Expected: startup time should be independent of keychain size — cert-anchor collection should not block the UI thread or run per-cert revocation-enabled trust evaluation.
Actual: startup freezes for a duration proportional to the number of certificates in the login keychain.
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
1.19367.0
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
<img width="512" height="512" alt="Image" src="https://github.com/user-attachments/assets/503ce5f9-acd1-4356-8da0-40e4419ec212" />