anthropics / anthropics/claude-code
[BUG] macOS Desktop: worktreeDepSeed's 680k-file clonefile starves a synchronous main-thread rename(2) — deterministic ~30 s whole-app freeze per new worktree session, discarded as "likely sleep"
- 主要语言
- Python
- 星标
- 145k
- 派生
- 23.1k
- PR 合并指标
- PR 指标待抓取
描述
### Preflight Checklist
- [x] I have searched existing issues. No issue mentions `worktreeDepSeed` or worktree dependency seeding. Nearest neighbours, both distinct and separated under "Related" below: #92325 (the sleep-heuristic accounting bug that hides *this* stall from telemetry) and #92785 (macOS desktop freezes on a similar cadence, but attributed to an EventEmitter leak — ruled out here with logs).
- [x] This is a single bug report.
- [x] I am using the latest version (Desktop 1.49585.0).
### What's Wrong?
Opening a new worktree-backed session in the Desktop Code tab blocks the **Electron main process for 26–38 seconds**, beachballing the entire app — every window, every session, not just the new one.
A `sample(1)` captured *during* a stall (condensed under **Error Messages/Logs** below) shows the mechanism is two overlapping pieces of the app's own work:
| thread | samples | doing |
|---|---|---|
| `com.apple.main-thread` | **20,683 / 21,070 (98.2%)** | blocked in a single synchronous `rename(2)` -> `__rename` |
| `tokio-runtime-worker` | **21,070 / 21,070 (100%)** | one recursive `clonefileat(2)` spanning the whole window |
The `node_modules` clone itself is correctly off the main thread. The problem is that the **main thread makes a blocking `rename(2)` while that clone is in flight**, and the clone -- a recursive in-kernel clone of **6.5 GB / 680,634 files** -- holds APFS metadata locks long enough to stretch that normally sub-millisecond `rename` to ~20.7 seconds. The main-thread call arrives from app JS (`CFRunLoop` source0 -> `node::InternalMakeCallback` -> JIT frames -> an fs binding); the JIT frames are unsymbolized, so the sample cannot name the renamed path.
Neither half freezes the app on its own. A sync fs call on the UI thread is invisible under normal I/O; the app only makes it a 30-second freeze by starving itself with its own clone.
This is not intermittent. **29 of 29 seed events in my logs took over 26 seconds:**
| | ms |
|---|---|
| n | 29 |
| min | 26,360 |
| median | 28,370 |
| max | 38,069 |
| mean | 29,455 |
One new worktree session = one guaranteed ~30 s whole-app freeze. In normal use that lands every 5–10 minutes, and it is hitting every engineer on my team who works in this repo.
**Three freezes from one morning, each with the stall and the seed emitted in the same second:**
```
2026-09-10 07:39:07 [warn] [event-loop-stall] main process blocked for 30202ms [likely sleep: duration_heuristic] (task WindowControl.reportSidebarPlaceholder=30285ms, total 8, cumulative 20066ms, rss 465MB)
2026-09-10 07:39:07 [info] [worktreeDepSeed] seeded for /.claude/worktrees/worktree-A in 31324ms (root 31103ms, workspaces=5, install_state=false)
2026-09-10 07:40:38 [warn] [event-loop-stall] main process blocked for 36850ms [likely sleep: duration_heuristic] (task WindowControl.reportSidebarPlaceholder=36857ms, total 8, cumulative 20066ms, rss 459MB)
2026-09-10 07:40:38 [info] [worktreeDepSeed] seeded for /.claude/worktrees/worktree-B in 38069ms (root 37674ms, workspaces=5, install_state=false)
2026-09-10 10:24:55 [warn] [event-loop-stall] main process blocked for 29114ms [likely sleep: duration_heuristic] (task WindowControl.reportSidebarPlaceholder=29111ms, total 9, cumulative 23736ms, rss 347MB)
2026-09-10 10:24:56 [info] [worktreeDepSeed] seeded for /.claude/worktrees/worktree-C in 30302ms (root 29891ms, workspaces=5, install_state=false)
```
The `sample(1)` covers that last one: sampling started at 10:24:25.924, one second after the worktree directory appeared, and ran 25 s -- inside the 29,114 ms stall that ended at 10:24:55.
The `root NNNNNms` figure is the root-tree clone alone; it brackets the stall almost exactly, which is the correlation the sample then explains.
**Secondary defect — this stall is invisible to your telemetry.** Both 30 s+ stalls above are tagged `[likely sleep: duration_heuristic]`, and a tagged stall increments neither `total` nor `cumulative`. Watch the counters across five consecutive stalls:
| time | blocked | tagged as sleep? | total | cumulative |
|---|---|---|---|---|
| 05:30:58 | 25,406 ms | yes | 6 | 17,978 ms |
| 06:26:15 | 1,504 ms | no | 7 | 19,482 ms |
| 06:54:20 | 584 ms | no | 8 | 20,066 ms |
| 07:39:07 | **30,202 ms** | yes | **8** | **20,066 ms** |
| 07:40:38 | **36,850 ms** | yes | **8** | **20,066 ms** |
A 584 ms stall is counted. Stalls of 30 s, 37 s and 29 s are not. The machine did not sleep — I was typing in the app when both fired, and the app logs an explicit `[event-loop-stall] OS resume` line on real wake (present at 05:21:22, absent at both 07:39 and 07:40). This is #92325 on macOS, and it is why a fully deterministic 30-second freeze has presumably never shown up in aggregate stall data.
### What Should Happen?
The main thread should not make blocking fs calls, and certainly not while the app is saturating the same volume. In rough order of cost to fix:
1. Make the main-thread `rename` async (or move it to a worker). This alone removes the freeze -- the clone can keep running as-is.
2. Failing that, do not overlap main-thread fs work with the seed clone: hold the rename until the seed's `clonefileat` completes.
3. Consider skipping or chunking the seed above a size/file-count threshold -- one recursive clone of 680k files is a very long metadata-lock hold regardless of which thread issues it.
4. Expose an opt-out setting for repos where the clone costs more than it saves.
Separately, the `duration_heuristic` sleep test needs a real corroborating signal (an `OS resume` event, a wall-clock-vs-monotonic delta) rather than duration alone, so stalls like this stop being discarded.
### Error Messages/Logs
Every seed event on this machine (`~/Library/Logs/Claude/main*.log`), showing that none completes in under 26 s:
```
2026-09-04 08:03:10 [info] [worktreeDepSeed] seeded for in 28135ms (root 27939ms, workspaces=5, install_state=false)
2026-09-04 08:03:42 [info] [worktreeDepSeed] seeded for in 32876ms (root 32325ms, workspaces=5, install_state=false)
2026-09-04 12:10:49 [info] [worktreeDepSeed] seeded for in 26968ms (root 26457ms, workspaces=5, install_state=false)
2026-09-05 06:45:02 [info] [worktreeDepSeed] seeded for in 27267ms (root 27089ms, workspaces=5, install_state=false)
2026-09-08 06:51:53 [info] [worktreeDepSeed] seeded for in 33252ms (root 32927ms, workspaces=5, install_state=false)
2026-09-08 06:54:09 [info] [worktreeDepSeed] seeded for in 28447ms (root 28264ms, workspaces=5, install_state=false)
2026-09-08 07:56:51 [info] [worktreeDepSeed] seeded for in 30996ms (root 30536ms, workspaces=5, install_state=false)
2026-09-08 08:38:41 [info] [worktreeDepSeed] seeded for in 28850ms (root 28344ms, workspaces=5, install_state=false)
2026-09-08 08:39:33 [info] [worktreeDepSeed] seeded for in 28443ms (root 28182ms, workspaces=5, install_state=false)
2026-09-09 05:49:29 [info] [worktreeDepSeed] seeded for in 26599ms (root 26301ms, workspaces=5, install_state=false)
... 13 more between 2026-09-09 05:51 and 2026-09-10 06:22, all 26.3-37.7s ...
2026-09-10 07:39:07 [info] [worktreeDepSeed] seeded for in 31324ms (root 31103ms, workspaces=5, install_state=false)
2026-09-10 07:40:38 [info] [worktreeDepSeed] seeded for in 38069ms (root 37674ms, workspaces=5, install_state=false)
```
Process-memory sample taken at a stall, showing the app is not memory-starved — this is pure main-thread blocking:
```
2026-09-10 06:26:16 [info] [process-memory] trigger=stall tree_rss_sum=3895MB tree_footprint_sum=3463MB electron(14)=2007MB children(38)=1888MB top=[electron_renderer:684:674MB electron_main:598:445MB ...] sys_free=5867MB/32768MB
```
Condensed main-thread stack from that sample (21,070 samples total; leaf held 20,683 of them). Frame symbolization in the Electron Framework is nearest-symbol noise — the reliable frames are the AppKit/CoreFoundation ones at the top and the libsystem_kernel ones at the leaf:
```
21070 Thread_7528979 DispatchQueue_1: com.apple.main-thread (serial)
21070 ElectronMain (in Electron Framework)
...
21070 -[NSApplication run] (in AppKit)
21070 -[NSApplication _nextEventMatchingEventMask:untilDate:inMode:dequeue:] (in AppKit)
21065 _DPSNextEvent (in AppKit)
21065 ReceiveNextEventCommon (in HIToolbox)
21059 _CFRunLoopRunSpecificWithOptions (in CoreFoundation)
20685 __CFRunLoopRun (in CoreFoundation)
20685 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
...
20684 node::InternalMakeCallback(...) (in Electron Framework)
20684 v8::Function::Call(...) (in Electron Framework)
20683 ??? (in ) <-- app JS, JIT, unsymbolized
...
20683 rename (in libsystem_kernel.dylib) + 16
20683 __rename (in libsystem_kernel.dylib) + 8
```
And, concurrently, on a separate thread for the entire window:
```
21070 Thread_8971372: tokio-runtime-worker
...
21070 clonefileat (in libsystem_kernel.dylib) + 8
```
The full 372 KB `sample` output contains no user paths, no repository or branch names, and no credentials (it is stacks plus a binary-images list, all under `/Applications/Claude.app`, `/usr` and `/System`). Happy to attach it or send it to a triage address on request.
### Steps to Reproduce
1. Use a monorepo with a large, gitignored `node_modules` (here: 6 bun workspaces; root `node_modules` 6.5 GB / 680,634 files, each workspace tree additionally seeded).
2. In the Desktop Code tab, start a new session with a worktree (any flow that leases a fresh worktree).
3. Immediately try to interact with **any** part of the app — a different session, the sidebar, the window controls.
4. The whole app beachballs for 26–38 s. It recovers on its own.
5. `grep -ahE "event-loop-stall|worktreeDepSeed" ~/Library/Logs/Claude/main*.log` — the stall and the seed appear in the same second, and the stall carries `[likely sleep: duration_heuristic]`.
Reproduces on every attempt, 29/29.
### Claude Model
Opus 5 (not model-dependent — the stall is in the desktop main process, before any model call)
### Is this a regression?
Yes
### Last Working Version
`worktreeDepSeed` does not appear anywhere in my rotated logs before **2026-09-04** (`main2.log`, `main3.log`, `main4.log`, covering 2026-08-12 → 2026-09-04: zero occurrences). First event 2026-09-04 08:03:10; volume steps up sharply from 2026-09-08 (23 events in `main.log`). So the feature landed around Sep 4 and the freezes date from there.
### Claude Code Version
Desktop 1.49585.0 (Electron 44.2.0), bundled CLI
### Platform
Anthropic API
### Operating System
macOS 26.6.2 (25G83), Apple M2 Max (Mac14,6), 32 GB RAM
### Terminal/Shell
Desktop app Code tab (zsh)
### Additional Information
- 24 live worktrees under `.claude/worktrees/` at time of report, so this fires many times a day.
- `install_state=false` on every event — whatever the install-state seeding step is, it is not contributing; the entire cost is the tree clone.
- From the bundle, the seed path is `sessions/worktreeDepSeed.ts` → `native.cloneAt` (APFS `clonefileat`), dispatched to a tokio worker, with outcomes `seeded | skipped_dest_exists | skipped_not_ignored | skipped_clone_unsupported | skipped_aborted | failed`. The copy-on-write blocks are cheap; the 680k-entry metadata work is not, and it is what starves the main thread's `rename`.
- I have not identified which path the main thread renames -- the calling JS frames are JIT and unsymbolized. `fs_usage -f filesys` during a seed would name it; happy to capture that if useful.
- The blocking task is consistently reported as `WindowControl.reportSidebarPlaceholder`, which looks like whatever was queued behind the seed rather than the cause.
### Related
- **#92325** — same `duration_heuristic` accounting bug, reported on Windows. This report is the macOS instance, and shows what the discarded stalls actually were.
- **#92785** — macOS desktop freezing on a similar 7–10 minute cadence, attributed to an EventEmitter listener leak. Ruled out on my machine: 110 `MaxListenersExceededWarning` lines total, most recent 2026-09-09 06:37, none within 25 hours of either freeze above.
贡献指南
这个仓库没有索引到贡献指南
调研方向
Start with the mentioned seed path, sessions/worktreeDepSeed.ts -> native.cloneAt, and reproduce from a large worktree-backed Desktop Code session while watching ~/Library/Logs/Claude/main*.log for worktreeDepSeed and event-loop-stall. The missing piece is the main-thread rename path; the report suggests fs_usage -f filesys during a seed. Done means new worktree seeding no longer freezes the whole app and long stalls are not discarded as likely sleep without corroboration.
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- electron, macos, node.js, typescript
- 领域
- desktop, observability, performance
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 活跃
- 描述清晰度
- 基本清楚
- 新手友好度
- 42/100