[BUG] `Monitor` `until`-loop self-matches when model uses `pgrep -f "<cmdline>"` as its check
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?
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
Monitortool 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:
- 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 andbash <file>, or pass the pattern via an environment variable and have the check read it from$CHECK_PATTERN. Either preventspgrep -fself-matching. - 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 topgrep -fotherwise because it's the most natural phrasing. - Monitor tool safety: apply a reasonable max wall-clock timeout on the outer
untilloop 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_
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗