Desktop app SIGKILLs its own Claude Code workers on a healthy Mac, then blames endpoint security (gates on raw free pages, not memory pressure)
Summary
The desktop app SIGKILLs its own Claude Code worker processes on a healthy machine, and reports the cause to the user as "Blocked by endpoint security — ask your IT team to allowlist signing Team ID Q6L2SF6YDW."
There is no endpoint-security software on this machine. The kills correlate with sys_free_raw (raw free pages) falling below ~800 MB — a metric that is near-zero on every healthy macOS system by design, because macOS deliberately uses idle RAM for cache and compressed pages.
Two bugs:
- The gate uses the wrong memory metric. Low raw-free memory is the normal steady state on macOS, not a distress signal. The OS exposes
kern.memorystatus_vm_pressure_levelfor exactly this purpose. - The error message misattributes the cause, sending users to their IT department for a problem the app created. On a personal Mac with no MDM there is nothing to allowlist.
Environment
- Claude desktop 1.37937.3 (also observed on 1.30096.5 and 1.32885.1 — this is not new)
- Claude Code 2.1.246
- macOS 26.6.1, Apple M4, 16 GB RAM, no MDM, no EDR, no MDM profiles
Evidence
The machine is not under memory pressure at any point:
| macOS metric | value |
|---|---|
| kern.memorystatus_vm_pressure_level | 1 (normal) |
| vm.swapusage used | 0.00 MB |
| memory_pressure "system-wide memory free" | 78% |
| Jetsam reports in /Library/Logs/DiagnosticReports | 0, ever |
| log show --predicate 'process == "kernel"' around each kill, matching kill/jetsam/memorystatus | 0 lines |
No endpoint-security software exists to have done it:
| check | result |
|---|---|
| systemextensionsctl list — endpoint security extensions | 0 |
| /Library/LaunchDaemons/ | empty |
| EDR processes (CrowdStrike, SentinelOne, Sophos, Defender, Jamf, Santa…) | 0 running |
| unified-log endpoint-security events in each kill window | 0 |
The app's own log shows it doing the killing. Every kill is recorded by the app with its own error class:
[error] Session <id> query error: Claude Code process terminated by signal SIGKILL
{ errorClass: 'process_killed_by_signal', signal: 'SIGKILL',
stack: 'Error: ... at vq.getProcessExitError (…/app.asar/.vite/build/index.chunk-*.js) …' }
The correlation — 4 kills in one day vs 211 [process-memory] readings
| kill time | tree_rss_sum | children | sys_free_raw | sys_free |
|---|---|---|---|---|
| 11:54:35 | 8928 MB | 142 | 63 MB | 2174 MB |
| 11:59:57 | 4419 MB | 29 | 350 MB | 3037 MB |
| 12:07:03 | 4692 MB | 39 | 697 MB | 3360 MB |
| 15:04:18 | 4195 MB | 26 | 268 MB | 4530 MB |
- Tree size does not discriminate — 4.2–8.9 GB and 26–142 children across the four kills.
sys_free_rawdoes — every kill occurred below 800 MB; not one occurred above it (0 of 30 readings ≥800 MB were followed by a kill within 60 s).- It is a precondition, not a trigger: 177 readings below 800 MB were not followed by a kill.
Note the 11:54 row: the app logged sys_free=2174MB alongside sys_free_raw=63MB at the same instant. It appears to gate on the raw figure.
Why this is the wrong metric
macOS keeps raw free pages near zero on purpose — unused RAM is used for file cache and compressed memory, and released on demand. A machine with 63 MB "free", 0 bytes of swap, and pressure level 1 is healthy. Gating on that figure means the app will reap workers on any well-utilised Mac, which is what happens here: the kills land while the OS itself reports normal pressure and has never once needed to jetsam a process.
Impact
- Long-running agent work is destroyed mid-flight, repeatedly (12 sessions killed in a single second in one batch).
- The error message sends users to IT for a non-existent security product. Considerable time was lost chasing that before the app's own logs identified the real killer.
- Workaround that shouldn't be necessary: keeping raw free memory artificially high by quitting other applications.
Suggested fixes
- Gate on
kern.memorystatus_vm_pressure_level(orDISPATCH_SOURCE_TYPE_MEMORYPRESSURE) rather than raw free pages. Optionally also require non-trivial swap activity before acting. - Never attribute a self-inflicted SIGKILL to endpoint security. If the app's own watchdog reaped the process, say so — and ideally log the metric and threshold that fired. The current message is actively misleading on machines with no security software installed.
- If a memory guard is genuinely required, warn before reaping so in-flight work can be checkpointed.
Reproduction
Not deterministic, but reliably reproduced by running several concurrent Claude Code sessions plus ordinary developer processes (a dev server, a browser) on a 16 GB Mac until sys_free_raw drops below ~800 MB — visible in the app's own [process-memory] interval log lines. Kills follow within ~30 s while macOS reports normal pressure and zero swap.
Happy to supply full anonymised log extracts on request.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Follow-up: two more kills tonight, with watchdog-race and restart-amplification evidence
Two further SIGKILLs on the same machine today (2026-08-28, ~18:06 and ~18:12 local), both showing the "Blocked by endpoint security / Team ID Q6L2SF6YDW" card. Still no endpoint-security software installed. This comment adds two mechanisms observed with hard data.
1. The kill races — and beats — an external mitigation sampling every 2 minutes
Since filing this issue I run a cron that samples free RAM and reaps stray processes below a threshold. Its log for tonight's first kill:
Free RAM collapsed to 76 MB in under one 2-minute sampling tick (a test suite + build burst), and the app's kill fired before the reap landed. Note also that the kill itself frees the dead worker's RSS, so any post-kill sample under-reports how low free RAM was at the trigger moment. All six kills observed on this machine to date are consistent with the <800 MB raw-free gate described in the original report; macOS memory pressure remained normal throughout.
2. SIGKILL amplifies its own trigger: orphaned MCP stacks and multiplied workers
Because the worker is SIGKILLed, it can never clean up its children. After tonight's two kills the process table held:
claude-codesession workers simultaneously (272–468 MB RSS each) — three of them spawned within 20 seconds during the crash-restart sequence, plus one surviving from the previous dayobsidian-mcp-pro, 34context-mode, 9fakechatMCP-server processes across those stacksSo each kill leaves the previous session's MCP servers resident, the restart spawns a fresh full stack, baseline memory ratchets upward, and the next kill arrives sooner — a feedback loop in which the watchdog manufactures the very condition it gates on. Tonight the second kill followed the first by roughly six minutes.
Happy to provide the raw
pssnapshots or guard logs if useful.Correction to my previous comment — point 2 was overstated
Following my own advice to verify rather than assume, I checked parentage on every process I cited, and I got part of it wrong.
The five concurrent
claude-codeworkers all have LIVE parents (the Claude.app process and aclaudeCLI process). They are legitimate concurrent sessions, not orphans left by SIGKILLs. I asserted a restart-amplification feedback loop; that specific mechanism is not established by my data and I withdraw it.What does hold up:
context-mode, anobsidian-mcp-proand its node child) were still resident — roughly 290 MB. My reaper cleared them between two consecutive checks, which is also why my follow-up snapshot showed 0 MB reclaimable. So SIGKILL does leak MCP stacks, since a killed worker cannot clean up its children; I just cannot substantiate the ratcheting-baseline story I built on top of it.Apologies for the noise. The unverified inference was mine, not the data's.
Adding a data point from a machine that should break one of the confounds here: 48 GB Mac Studio, macOS 26.6.2, Desktop 1.37937.1 (yours is .3), same message, same
Q6L2SF6YDW,systemextensionsctl list→ 0 extensions, no MDM/EDR of any kind. Four of my kills also landed on 2026-08-27.On the
sys_free_rawthreshold — I think it may be a base-rate artifact, and I'd rather say so than quietly agree.I pulled every
[process-memory]sample and every exit-137 from my rotated logs: 44,200 samples, 12 kills. On this machinesys_free_rawhas no predictive power:| | |
|---|---|
| median
sys_free_raw| 232 MB || minimum observed | 14 MB (no kill) |
| share of all samples under 800 MB | 95% |
| kills under 800 MB | 11 of 12 — chance predicts 11.4 |
Ranking each kill against the machine's own distribution:
Mean kill percentile is 71st — kills happen at higher free memory than typical, and the machine spends half its life below the lowest kill value without anything dying. I don't think this refutes your correlation on a 16 GB M4, where samples above 800 MB may be genuinely rare; it does suggest the threshold needs testing on a machine where that line isn't nearly always crossed. If you can post your sample count and median
sys_free_raw, that would settle whether we're looking at the same effect.Two artifacts I haven't seen quoted elsewhere in this family.
The classification is a named telemetry category, not just the dialog string:
And I get a different code path to yours —
errorClass: 'process_exited_nonzero'with exit 137, where you haveprocess_killed_by_signal/SIGKILL. Same 128+9 event, but reaching the app as a wait-status rather than a delivered signal. Both funnel into the sameendpoint_security_blockedcategory, which supports the mapping being unconditional.Timing argues against a memory watchdog reaping a heavy worker. The child dies within ~1s of spawn, before the first stdin write lands:
had_first_response: false,seconds_to_outcome: 2. Nothing has run yet, so there is no large worker to reap — and it survived a full app relaunch mid-burst. It is also intermittent under identical conditions: 15 healthy vs 8 unhealthy cycles on the same day, same config.Separately, whatever the cause turns out to be: the dialog telling users to ask IT to allowlist Anthropic's own Team ID is actively harmful on a machine with no endpoint security. It generates support tickets that cannot be closed. A fallback along the lines of "the helper process was terminated unexpectedly" would cost nothing and mislead nobody.