[BUG] macOS: fseventsd runaway on agent-heavy Macs (24 GB, 800x its budget, a core pinned for days, no self-recovery) — Claude Code can block on it at startup (#96008) and nothing detects it

Status Open
Reported on v2.1.280
Maintainer reply None cached
Activity 5 comments · opened Oct 1, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet (related, closed: #96008, #72394; see below)
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

On a Mac used for heavy Claude Code work, Claude Code kept running for days on top of a crippled fseventsd: 24 GB against Apple's own 30 MB budget (launchctl print system/com.apple.fseventsd | grep 'jetsam memory limit (active': jetsam memory limit (active, soft) = 30 MB), one core pinned for about two days after the burst of file activity was over, swap at 13,548 of 14,336 MB. Claude Code neither detects this state nor, per the last comment on #96008, survives it gracefully: watcher registration on the main thread can block startup for a minute. The daemon did not recover by itself over the 46 hours I could see; a restart (sudo kill -TERM) reset it.

From Apple's diagnostic reports and the daemon's own log IDs:

| | boot → 28 Sep 10:15 | 28 Sep 10:15 → 30 Sep 01:39 | 30 Sep 01:39 → 1 Oct 23:00 | 1 Oct 23:20 |
|---|---|---|---|---|
| file events per second, daily average | 50–200 (14–21 Sep), rising to 4,694 (27 Sep) | 5,715 (28 Sep), 700 (29 Sep) | 1,107 (30 Sep) | 195 (30-second measurement) |
| fseventsd share of one core | 1.5 % | 16 % average (63 % in a macOS report at 28 Sep 23:20) | 97–98 % | pinned |
| fseventsd memory | 10–42 MB (27 Sep → 28 Sep 07:59) | 0.04 GB (28 Sep 10:15) → 1.7 GB (28 Sep 23:20) → 7.93 GB (30 Sep 01:39) | 11.17 GB (30 Sep 06:27) | 24 GB |

The load fell eightfold after 28 Sep; the daemon's CPU use went up. Root sample of the daemon: its disk-log thread spent 93 % of samples in _platform_strncmp; the delivery thread waited on a mutex about half the time. Only 176 Mach ports, so this is accumulated state, not many live clients. launchctl kickstart -k is refused by SIP (error 150); sudo kill -TERM $(pgrep -x fseventsd) worked and launchd respawned it: 7.4 MB, under 1 % CPU, swap down to 1.5 GB.

Full write-up with every number, the methods and the scripts: https://github.com/talkstream/fseventsd-runaway

Why this is a Claude Code issue, and what I am not claiming:

  • #96008 was closed by its own reporter on 23 Sep after he found the trigger; its last comment names "the remaining Claude Code bug: FSEvents watchers (skills, commands, settings) are registered in a way that blocks the main thread, so a slow fseventsd freezes all input at startup" (2.1.280). That part was never picked up. This report is about it, with the daemon-side data behind it.
  • The team already knows the state exists: CHANGELOG 2.1.271, "Fixed settings file changes made outside the session going unnoticed on macOS machines whose system file-event service is saturated; the watcher now falls back to polling". Users are still not told, and the machine stays crippled.
  • Claude Code is an FSEvents client: the docs say "Claude Code watches ~/.claude/agents/ and .claude/agents/", #19566 describes "many FSEvents watchers (for config files, skill directories, settings, etc.)", and in lsmp -p <fseventsd pid> on my Mac the two CLI session processes each held two send rights to fseventsd, while the claude daemon process and its two bg-pty-host helpers held none.
  • The 2.1.286 binary references FSEventStreamCreate, Start, Stop, Invalidate, Release and ScheduleWithRunLoop; the string FSEventStreamSetExclusionPaths does not appear in it (strings -a … | grep FSEventStream).
  • I am not claiming Claude Code caused the leak. During the flood (22–28 Sep) the machine ran 8,900 to 19,500 Claude Code tool calls a day plus mutation testing and Docker via Colima; daily tool calls correlate with daily log-file counts at only r = 0.71. The leak itself looks OS-side, and I am reporting it to Apple. Compare daintreehq/daintree#12455: a 36 GB fseventsd with about 12 claude CLIs running; the author's hypothesis is per-client state leaking under register/unregister churn, marked "not proven".

What Should Happen?

  1. Detect it. On macOS, /doctor and a cheap check at session start compare fseventsd's footprint with its launchd jetsam budget and warn when it is far over, with the fix command. A working, sub-second, no-sudo implementation is in the linked repo as a SessionStart plugin (plugin/fseventsd-health, 8 selftests, about 0.3 s); I am happy to upstream it into /doctor or plugins/ if you want it.
  2. Never block the main thread on FSEventStreamStart (the open remainder of #96008; the same stack appears in manaflow-ai/cmux#12313 on macOS 27.0): start watchers asynchronously with a timeout, so a slow fseventsd degrades file watching instead of freezing input.
  3. Reduce churn. Keep one long-lived stream per process instead of rebuilding it when a watched path is added (Node's libuv rebuilds the stream on every change; I could not check what the Bun build does), exclude hot directories with FSEventStreamSetExclusionPaths (max 8), and skip watchers in processes that do not need live reload, such as claude -p and subagent processes.

Error Messages/Logs

$ launchctl print system/com.apple.fseventsd | grep 'jetsam memory limit (active'
	jetsam memory limit (active, soft) = 30 MB
$ sudo footprint <pid>
fseventsd [551]: 64-bit    Footprint: 24 GB (16384 bytes per page)
  24 GB        0 B          0 B       8162    Malloc Small
$ sudo launchctl kickstart -k system/com.apple.fseventsd
Could not kickstart service "com.apple.fseventsd": 150: Operation not permitted while System Integrity Protection is engaged
macOS cpu_resource report, 28 Sep 23:20: "90 seconds cpu time over 143 seconds (63% cpu average), exceeding limit of 50% cpu over 180 seconds" / "Action taken: none"
sample (root), disk-log thread: 2351 of 2526 samples in _platform_strncmp

Steps to Reproduce

Not deterministic; it builds up over days. To observe it:

  1. On macOS, run agent-heavy Claude Code work for several days (many parallel sessions and subagents, test runners, mutation testing, containers with the home directory mounted).
  2. Check daily: top -l 1 -stats pid,mem,cmprs,ports -pid $(pgrep -x fseventsd) and compare with launchctl print system/com.apple.fseventsd | grep 'jetsam memory limit (active'.
  3. Once it is far over budget, note that it keeps growing and keeps a core busy even after the file-event rate falls back, and that Claude Code shows no warning.
  4. sudo kill -TERM $(pgrep -x fseventsd) resets it (save sudo ls -laT /System/Volumes/Data/.fseventsd first: the restart erases the event history).

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Claude Code Version

2.1.286 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

MacBook Pro 14" M3 Pro (Mac15,6), 18 GB, macOS 27.0 (26A428), uptime 20 days at diagnosis. Related: #96008, #72394, #19566, #66020; daintreehq/daintree#12455; abiosoft/colima#1569; manaflow-ai/cmux#12313.

View original on GitHub ↗

3 Comments

talkstream · 13 days ago

I tried to open a pull request with the detection plugin from point 1, but pull-request creation on this repository is limited to collaborators (createPullRequest is refused for outside accounts). The change is ready on a fork branch instead:

https://github.com/anthropics/claude-code/compare/main...talkstream:claude-code:plugins/fseventsd-health

It adds plugins/fseventsd-health/ (plugin.json, hooks.json, handler, README, selftest), one entry in .claude-plugin/marketplace.json and one row in plugins/README.md. Read-only SessionStart hook, macOS only, about 0.3 s, silent while fseventsd is healthy; bash plugins/fseventsd-health/tests/selftest.sh passes 8/8 and fails under SELFTEST_BREAK=1. Happy to adapt it, or for you to fold the check into /doctor.

ayodm · 12 days ago

A data point from two agent-heavy Macs, which may help separate "many Claude processes" from "watcher churn" as the trigger.

Setup: MacBook Pro (Mac14,10, 16 GB, macOS 15.7.3, Claude Code 2.1.285, uptime 5 days) and Mac mini (Mac16,10, 16 GB, macOS 26.3, uptime 5 days). Measured 2026-10-01 with the commands from the report.

fseventsd was healthy on both, against the same 30 MB budget:

| | footprint | CPU | Mach ports | process age |
|---|---|---|---|---|
| MacBook Pro | 6.6 MB | 0.0 % | 594 | 5 d |
| Mac mini | 6.3 MB | 0.0 % | 285 | 5 d |

On the MacBook this was despite a pile-up of headless Claude Code processes. A third-party plugin, pinned at an old version, started a headless claude --output-format stream-json process per session and never ended it. 31 had accumulated over ~29 hours, holding 3.6 GB RSS, mostly idle. The plugin had been fixed upstream long ago; plugins from third-party marketplaces don't auto-update by default, so it sat on the leaking version for months. Updating it removed the leak.

What this suggests (inferred, not proven):

  • A large number of mostly idle CLI processes did not, on its own, push fseventsd over budget here. That fits the report's "churn, not count" reading and the daintree hypothesis about register/unregister cycles, more than a simple client count.
  • It still supports point 3. Headless --output-format stream-json / SDK processes like these have no use for settings, skills or agents live-reload watchers. If Claude Code skipped FSEvents registration in headless/SDK mode, a leaking plugin or orchestrator couldn't multiply watcher clients at all.
  • Small addition to point 1: if /doctor warns on fseventsd, it could also count long-lived headless claude processes and flag stale plugin versions. A leaked-process pile-up is easy to miss, and it is the scenario that turns into churn.

Caveat: we couldn't confirm whether those headless processes held fseventsd send rights (lsmp without root returned none; inconclusive).

talkstream · 11 days ago

@ayodm thank you, that is a useful control. A follow-up from my side, with one correction to how I would read my own data.

A fresh daemon stayed healthy through a heavy agent run, but that run was not a file-event flood. After the 1 Oct restart I updated to macOS 27.0.1 (26A434) and rebooted. Over the next 42 hours the hourly watch log shows 16 checks, all within budget at 4.6–5.5 MB. That window included one session with eight parallel agents; between 19:17 and 21:40 the session fetched 61 short videos and software-decoded at least 58 of them; most likely the machine got hot from that CPU load. Read as root, the event log for that window shows 22–190 events/s per hour. That is an ordinary rate; the flood day (28 Sep) averaged 5,715/s. So my run says nothing about churn at flood scale, only that heavy CPU work at normal event rates does not trigger it.

Taken together: 31 mostly idle headless processes (your MacBook; whether they held fseventsd send rights is open, as you note) and heavy CPU work at ordinary event rates (mine) both left the daemon at about 5–7 MB. On my machine the 24 GB state came after days of flood (22,067 log files on the peak day against 95–600 on normal days) and then did not come back down. One caveat: my "before" and "after" are on different builds (27.0 vs 27.0.1).

On your lsmp caveat: without root it fails with task_for_pid() failed. As root, sudo lsmp -p $(pgrep -x fseventsd) works. Mine held 176 Mach ports at 24 GB, and 211 at 5 MB on 3 Oct, against your 594 and 285 at about 6 MB. Port count does not track the growth, which also argues for accumulated internal state rather than live clients.

+1 to skipping FSEvents registration in headless/SDK mode, and to /doctor counting long-lived headless processes. Both shrink the churn surface without needing to know the leak mechanism.

Write-up updated: https://github.com/talkstream/fseventsd-runaway#11-follow-up-23-oct-hot-again-and-it-was-not-fseventsd

Showing cached comments. Read the full discussion on GitHub ↗