claude-code - 💡(How to fix) Fix [BUG] `Monitor` `until`-loop self-matches when model uses `pgrep -f "<cmdline>"` as its check [1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#52328Fetched 2026-04-24 06:10:04
View on GitHub
Comments
1
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
labeled ×4commented ×1

Error Message

  1. Tool description: explicitly warn against pgrep -f "<cmdline>" as a check and recommend alternatives: kill -0 <pid>, tail -f <output> | grep -m1 <marker>, or a sentinel file. Many models will default to pgrep -f otherwise because it's the most natural phrasing.

Error Messages/Logs

Root Cause

Claude Code's parent process cannot exit because it's still awaiting the Monitor tool result, which will never arrive. In headless mode (claude -p), this hangs the entire invocation indefinitely.

Code Example

until ! pgrep -f "dotnet test <args>" > /dev/null; do sleep 20; done

---

$ ps --ppid <claude_pid> -o pid,cmd
 PID  CMD
N    /usr/bin/bash -c ... eval 'until ! pgrep -f "dotnet test <args>" > /dev/null; do sleep 20; done; ...'

$ cat /proc/N/wchan
do_wait            # blocked on the child sleep

$ pgrep -af "dotnet test <args>"
N /usr/bin/bash -c ... 'until ! pgrep -f "dotnet test <args>" ...'
#   ^ the Monitor bash matches itself

# The actual background command finished hours ago; no other process matches.

---
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Monitor until-loop self-matches when model uses pgrep -f "<cmdline>" as its check

Environment

  • Claude Code (Opus, but pattern is model-agnostic — generated bash is the same)
  • Linux, bash
  • Monitor tool with the documented polling pattern (until <check>; do sleep N; done)

What happens

When the model invokes Monitor with a pgrep-style check like:

until ! pgrep -f "dotnet test <args>" > /dev/null; do sleep 20; done

…the Monitor tool spawns the loop inside a bash -c '<the whole until expression>'. That bash's own argv contains the literal string the check is searching for. pgrep -f matches against the full command line, so it finds the Monitor's own bash process. pgrep returns 0, ! negates to false, and the loop never exits — even after the real background command has completed long ago.

Evidence from a hung session

$ ps --ppid <claude_pid> -o pid,cmd
 PID  CMD
N    /usr/bin/bash -c ... eval 'until ! pgrep -f "dotnet test <args>" > /dev/null; do sleep 20; done; ...'

$ cat /proc/N/wchan
do_wait            # blocked on the child sleep

$ pgrep -af "dotnet test <args>"
N /usr/bin/bash -c ... 'until ! pgrep -f "dotnet test <args>" ...'
#   ^ the Monitor bash matches itself

# The actual background command finished hours ago; no other process matches.

Claude Code's parent process cannot exit because it's still awaiting the Monitor tool result, which will never arrive. In headless mode (claude -p), this hangs the entire invocation indefinitely.

Why it matters

This is a plausible default check for models instructed to "wait for a background command to finish." The self-match trap is subtle — it's not obvious from reading the generated bash, and the surface symptom ("claude hangs forever") is easy to misattribute to MCP teardown or network issues. I wasted time chasing those red herrings before tracing it.

Suggested directions

Any of these would fix it; they're not mutually exclusive:

  1. Monitor tool implementation: invoke the check through a wrapper that doesn't leak the pattern into the shell's argv. e.g. write the check command to a temp file and bash <file>, or pass the pattern via an environment variable and have the check read it from $CHECK_PATTERN. Either prevents pgrep -f self-matching.
  2. Tool description: explicitly warn against pgrep -f "<cmdline>" as a check and recommend alternatives: kill -0 <pid>, tail -f <output> | grep -m1 <marker>, or a sentinel file. Many models will default to pgrep -f otherwise because it's the most natural phrasing.
  3. Monitor tool safety: apply a reasonable max wall-clock timeout on the outer until loop so a self-matching check eventually fails rather than hanging forever.

Minimal repro

Ask an agent (any model) to run a short background command and then use Monitor to wait for it using pgrep -f "<the exact command>". The Monitor never exits even after the command completes.

What Should Happen?

Monitoring a process should not hang claude.

Error Messages/Logs

Steps to Reproduce

Minimal repro

Ask an agent (any model) to run a short background command and then use Monitor to wait for it using pgrep -f "<the exact command>". The Monitor never exits even after the command completes.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

No response

Claude Code Version

2.1.117 (Claude Code)

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

No response

extent analysis

TL;DR

The Monitor tool's until loop can hang indefinitely when using pgrep -f checks due to self-matching, and a workaround is to modify the check command to avoid leaking the pattern into the shell's argv.

Guidance

  • Modify the Monitor tool to invoke the check through a wrapper that doesn't leak the pattern into the shell's argv, such as writing the check command to a temp file and executing it with bash <file>.
  • Use alternative checks that don't rely on pgrep -f, such as kill -0 <pid> or tail -f <output> | grep -m1 <marker>.
  • Apply a reasonable max wall-clock timeout on the outer until loop to prevent indefinite hanging.

Example

# Instead of this
until ! pgrep -f "dotnet test <args>" > /dev/null; do sleep 20; done

# Use a wrapper script
echo "pgrep -f 'dotnet test <args>'" > check.sh
chmod +x check.sh
until ! ./check.sh > /dev/null; do sleep 20; done

Notes

The issue is specific to the Monitor tool's implementation and the use of pgrep -f checks. The suggested workarounds and modifications should be applied to the Monitor tool or the check commands to prevent self-matching and indefinite hanging.

Recommendation

Apply a workaround by modifying the check command to avoid leaking the pattern into the shell's argv, such as using a wrapper script or an alternative check method. This will prevent the Monitor tool from hanging indefinitely due to self-matching.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] `Monitor` `until`-loop self-matches when model uses `pgrep -f "<cmdline>"` as its check [1 comments, 1 participants]