Skip to content

Bash tool: grep→ugrep shim children not killed on timeout; runaway memory (100+ GB) on long-line files #76056

Description

@eldiegod

Environment

  • Claude Code 2.1.204 / 2.1.205, macOS (Darwin 25.5.0), zsh
  • Model: claude-fable-5

Summary

The Bash tool injects a grep shell function that re-executes the claude binary as embedded ugrep (ARGV0=ugrep $CLAUDE_CODE_EXECPATH -G --ignore-files --hidden -I --exclude-dir=.git ...). Two problems compound:

  1. Orphaned children on timeout. When a Bash tool call times out, the spawned ugrep process is not killed. It keeps running (and allocating) indefinitely, invisible to the session that spawned it.
  2. Pathological memory on single-line files. On minified HTML (~300-600 KB, one long line) with -o and bounded-repetition patterns like [^>]{0,90}(foo|bar)[^<]{0,90}, embedded ugrep's RSS grows unbounded — observed 8 GB RSS in 45 seconds on a 263 KB file, VSZ ~440 GB per process.

A background session that repeatedly grepped fetched HTML in /tmp accumulated 6+ orphaned ugrep processes (0.5–8 GB RSS each), pushing Activity Monitor's App Memory for the terminal's tree past 126 GB and the machine into heavy swap. The session itself never saw an error — each call just timed out and it retried, spawning another orphan.

Repro sketch

  1. Save any large minified single-line HTML file (e.g. a marketing page) to /tmp/page.html.
  2. In a Claude Code session, have the Bash tool run: grep -oiE '[^>]{0,90}(alpha|beta|gamma)[^<]{0,90}' /tmp/page.html
  3. Watch RSS of the resulting ugrep process grow multi-GB; let the tool call hit its 2-minute timeout.
  4. ps aux | grep ugrep → the process survives the timeout and keeps growing.

Expected

  • Bash tool timeout should kill the whole process group (SIGTERM then SIGKILL), not just abandon the foreground wait.
  • Ideally the ugrep shim should bound worst-case memory (or fall back to system grep) on files with extremely long lines.

Activity

  1. antonioaraujo664 commented on Jul 14, 2026

    @antonioaraujo664

    Still reproducing on 2.1.205, macOS 26.5.2 (Darwin 25.5.0), Apple Silicon — inside Claude Desktop's local agent mode. Adding a data point with two extra angles this thread doesn't cover yet:

    1. CPU exhaustion, not just memory — with a different regex class. The pathological pattern here was a multi-lookahead PCRE, not a bounded {0,N} quantifier:

    ugrep -G --ignore-files --hidden -I --exclude-dir=.git [...] -rliP (?i)(?=.*kw1)(?=.*kw2)(?=.*(kw3|kw4|kw5)) .
    

    Two of these (from a single session grepping a directory of AI-session transcript files whose .jsonl entries are single multi-MB lines — exactly the long-line trigger described here) ran for 2h+ at ~300% CPU each before I found them via ps. So the shim-orphan problem manifests as CPU-bound too, not only RSS-bound.

      PID  PPID  %CPU     ELAPSED COMM
    31296     1 312.5  02:10:58 ugrep
    36262     1 304.9  02:00:57 ugrep
    

    (lsof -d cwd reports the executable command name as claude — i.e. the bundled binary re-exec'd as ugrep — confirming these are the grep→ugrep shim children, not a system ugrep.)

    2. Not a plain timeout — auto-backgrounding never gets reaped. In my case the Bash tool didn't just time out; after ~2 min it auto-moved the command to a background task with the message "Command running in background with ID: … You will be notified when it completes." Since the grep never completes, that completion notification never fires, nothing ever reaps the task, and there's no watchdog/time-cap — so it runs forever. The originating session stayed alive and healthy the whole time; it simply moved on and had no signal to clean up. The task's output file still held only the initial echo header (a few dozen bytes) after 2+ hours of CPU.

    Reinforces the fix already suggested in this thread (kill the shim child's process group when the tool call ends/times out), plus: a time-cap/watchdog on auto-backgrounded Bash tasks, and reaping outstanding background tasks when a session ends.

  2. agda-hd-ai commented on Aug 4, 2026

    @agda-hd-ai

    Another data point — same two mechanisms, but a slow linear profile ending in a host OOM

    Production incident, 2026-08-03, ~40 min of downtime on a shared host running production mail, a secrets store, internal DNS and monitoring. Confirms both mechanisms in this issue and adds a growth profile that differs from the ones reported so far.

    Environment

    • Claude Code 2.1.220, native binary, Linux (Ubuntu), zsh
    • 7.7 GB RAM host, agents launched from SSH sessions
    • The process name in the kernel OOM log was 2.1.220, not ugrep — because the shim does exec -a ugrep "$CLAUDE_CODE_EXECPATH". Worth noting for anyone triaging: the OOM line does not mention ugrep at all, which is why it took us ~40 minutes to identify the culprit.

    What happened

    The agent typed grep -rnE '<regex with UNICODE chars>' docs/. It got the shim:

    ARGV0=ugrep $CLAUDE_CODE_EXECPATH -G --ignore-files --hidden -I --exclude-dir=.git …
    

    Two invocations exceeded the Bash tool's 120 s timeout and were moved to the background by the harness. The agent treated that as a tool problem, worked around it with a different approach one minute later, and never killed the background tasks — there is no lifetime bound and no reminder for a task that never completes.

    Measured growth — this is the part that differs from existing reports

    time (UTC) RSS
    21:19 4 572 344 kB
    21:23 4 826 452 kB

    ⟹ +63 MB/min, linear, no spike. Total lifetime ~30 min, ending at 5.75 GB anon-rss (74 % of host RAM).

    This is not the fast DFA-compilation blow-up described in #82230 / #67021 / #83342 — those allocate GBs in seconds, before reading input. Here the allocation was slow and monotonic over half an hour. Two OOM kills, both UID:1003:

    21:17:06  Out of memory: Killed process 4184312  anon-rss:3627644kB  UID:1003
    21:57:46  Out of memory: Killed process 4185719  anon-rss:5756416kB  UID:1003
    

    Why the slow profile matters for the fix

    A fast blow-up is at least visible — the tool call dies quickly. A slow linear leak in a backgrounded child is invisible from inside the session by construction:

    • the agent has moved on to other work,
    • the harness notifies on completion, so a task that never completes never reports,
    • and from outside it looks like a gradually degrading host, not a runaway process.

    The 120 s backgrounding turned a single slow command into a 30-minute leak. The backgrounding itself is presented as a convenience, but it transfers ownership to nobody.

    Suggestions (in order of leverage, from the operator side)

    1. Kill backgrounded Bash children when the turn/session that spawned them ends — this alone would have prevented the incident.
    2. Surface still-running background tasks in the session (a periodic "N background tasks still running, oldest 28 min" line), since only completion is currently reported.
    3. An RLIMIT_AS on the shim child would bound damage without needing to predict the pathological pattern.
    4. Separately, please consider Add an opt-out for the built-in find→bfs / grep→ugrep shadow functions injected into the Bash tool shell #69736 (opt-out for the shadow functions) — the substitution is invisible to the user who typed grep, and here it silently added --hidden to a recursive search nobody scoped.

    What we did on our side

    Nothing in this issue is blocked on you for us: we are adding a per-account cgroup memory limit so a single runaway command cannot take the host down. Filing this because the slow-linear variant does not appear in the existing reports, and because the exec -a argv rewriting makes the OOM line unattributable without knowing the shim exists.

  3. github-actions commented on Sep 8, 2026

    @github-actions

    Closing for now — inactive for too long. Please open a new issue if this is still relevant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleIssue is inactive

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions