[BUG] Workflow tool: runner subprocess leaks memory unbounded (~70 MB/s) → OOM-killed on long multi-agent runs

Resolved 💬 2 comments Opened Jun 17, 2026 by shacow Closed Jun 17, 2026

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 Code

What's Wrong?

Running a long, multi-agent "dynamic Workflow" (the Workflow tool) in WSL2 intermittently leaks memory until the process is OOM-killed and the terminal session dies with "[process exited with code 1 (0x00000001)]".

The leak is NOT in the interactive TUI. During Workflow execution there are two claude processes: the main TUI, and a SEPARATE child claude process that runs the workflow (its process title is set to the version string, so it appears in ps/OOM logs as e.g. 2.1.179). On an affected run, that runner subprocess's anonymous memory climbs steadily at ~70 MB/s with no upper bound until it consumes the entire WSL memory allocation (~32 GB) and is SIGKILLed by the Linux OOM-killer. The TUI stays healthy the whole time (~300–500 MB).

The most important clue: the growth is completely decoupled from the actual workload. A run that OOM'd at 31.9 GB and a run that completed cleanly were near-identical in every recorded metric:

| Metric | OOM run | Clean run |
| --- | --- | --- |
| subagents | 13 | 14 |
| Bash tool calls | 127 | 124 |
| transcript bytes | 3.19 MB | 3.29 MB |
| total tool output | 2.23 MB | 2.22 MB |
| peak runner RSS | 31.9 GB | < 0.6 GB |

So ~32 GB of anonymous heap is wildly disproportionate to the ~3 MB of data the run actually handled — it's an unbounded internal allocation, not retention of legitimate data. It is intermittent (most runs are fine), which points to a timing/nondeterministic trigger rather than any specific input.

What Should Happen?

The Workflow runner subprocess's memory should stay bounded and roughly proportional to the data it processes. A ~14-agent run streaming ~1.5M subagent tokens (~3 MB of transcripts) over ~15 minutes should use well under 1 GB — as the healthy runs do (peak < 600 MB). It should never be able to grow unbounded to tens of GB and get OOM-killed.

Error Messages/Logs

Linux OOM-killer (journalctl -k / dmesg) at the moment of the crash:

  systemd invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0
  oom-kill:constraint=CONSTRAINT_NONE,...,global_oom,task_memcg=/init.scope,task=2.1.179,pid=3959,uid=1000
  Out of memory: Killed process 3959 (2.1.179) total-vm:33809980kB, anon-rss:31943424kB, file-rss:296kB, shmem-rss:0kB, UID:1000 pgtables:62908kB oom_score_adj:0
  oom_reaper: reaped process 3959 (2.1.179), now anon-rss:0kB

OOM "Tasks state" table — note TWO claude processes; the TUI is fine, the workflow-runner child is the hog (RSS in 4 KB pages):

  [ 1168] ... 78061 pages  ( ~305 MB )  claude     <- interactive TUI, healthy
  [ 3959] ... 7985930 pages (~31.9 GB)  2.1.179    <- workflow runner subprocess (anon-rss 31,943,424 kB), OOM victim

Separate minor bug found while reproducing: passing the Workflow `args` as a bare (non-JSON) string makes the workflow runtime throw immediately —
  Error: JSON Parse error: Unexpected identifier "do"  at workflow.js:9:49
(harmless: fails in ~8 ms with 0 agents — but worth hardening the args parsing.)

Steps to Reproduce

NOTE: intermittent — reproduces on a minority of runs. I caught it roughly 1-in-several; two controlled re-runs under a memory watchdog (including the exact same input as a crashing run) both stayed clean. So it's nondeterministic/timing-based, not input-driven.

  1. In WSL2 (Linux), session model = Opus 4.8 (claude-opus-4-8[1m]).
  2. Run a long, fan-out dynamic Workflow via the Workflow tool: ~13–14 subagents (a parallel/parallel() discover phase of ~10 agents plus a couple of survey agents and a synthesis agent), each doing several Bash/Read tool round-trips, ~1.5M subagent tokens total over ~15 minutes. (Mine was a custom "field-note-scan" workflow: refresh a local dir, fan out ~10 parallel "miner" agents over text files + git history, then one synthesis agent.)
  3. Externally watch the separate child claude process (process title = version string) via ps//proc/<pid>/status, or just sample MemAvailable in /proc/meminfo.
  4. On an affected run, that runner subprocess's anonymous RSS climbs steadily (~70 MB/s) unrelated to the data, until it hits the WSL memory cap and is OOM-killed. Most runs instead stay flat (< 600 MB) and complete normally.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

(N/A — intermittent; seen across 2.1.170 and 2.1.179.)

Claude Code Version

2.1.179 (Claude Code) — also observed on 2.1.170

Platform

Claude subscription

Operating System

Windows

Terminal/Shell

WSL (Windows Subsystem for Linux)

Additional Information

ENVIRONMENT

  • WSL2 (Ubuntu) on Windows 11; kernel 6.18.33.1-microsoft-standard-WSL2, x86_64; host has 64 GB RAM.
  • .wslconfig: memory=32GB, processors=8, swap=0 (also reproduced/observed with swap tried at 8/16 GB).
  • The 32 GB WSL cap is why the OOM-kill happens inside the VM rather than wedging Windows.

KEY FINDINGS (from forensic analysis of the crash + 2 controlled re-runs)

  • The leak is in a SEPARATE child claude process that executes the Workflow, NOT the interactive TUI (which stayed ~305–500 MB throughout). Only the runner subprocess balloons.
  • Growth is steady/linear at ~70 MB/s and DECOUPLED from the workload (see the table above. "What's Wrong?"). 31.9 GB of anon heap vs ~3 MB of actual data handled.
  • Intermittent: could not force it on demand — 2 faithful re-runs (one with byte-identical input to a crashing run) both stayed flat (MemAvailable never dropped below ~30.7 GB).

RULED OUT (with evidence, so you don't chase these)

  • Model / model-switching: pure Opus 4.8[1m], no Fable, no auto-mode swap.
  • LSP servers: none were running (workspace had nothing the enabled LSPs index).
  • WSL /mnt/c 9p page cache: the OOM was anon-rss (process heap), not file cache.
  • Large input data: tiny (64 KB primary file; ~2.1 MB total corpus; subagent transcripts ≤ 552 KB).
  • Project hooks / MCP servers: none configured.
  • The bare-string args JSON.parse error: inert (0 agents, ~8 ms) — not the cause.

SECONDARY OBSERVATIONS

  • With swap=0, as it approaches the cap the kernel enters a reclaim livelock (pinned CPU + heavy SSD I/O) for a while before the kill; a small swap smooths this but doesn't stop the leak.
  • While any background Bash task runs, Claude Code rapidly spawns pairs of short-lived (~7 MB) claude helper subprocesses — harmless/transient, but possibly wasteful.

WORKAROUND

  • Running the same fan-out work via the Task/Agent tool (in-process subagents) instead of the Workflow tool stays flat (~500 MB). The unbounded growth appears specific to the Workflow tool's runner-subprocess execution path.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗