pgrep -f and pkill -f match the full command line of every process — including the shell running your own command. If your command text contains the pattern, it matches itself. This produces permanent false positives in monitors and self-terminating scripts.

The trap

-f matches argv, not just the executable name. A bash -c 'pgrep -f marker_single' has the literal string marker_single in its own argv, so it always finds at least one match — itself.

Over SSH this is especially easy to hit: the entire remote command string becomes one process’s argv on the remote host.

Incident 1 — false positive monitor (never completes)

ssh host 'pgrep -f marker_single >/dev/null && echo RUNNING || echo NO'

Always reported RUNNING, because the remote bash -c command line literally contained marker_single. The OCR job had actually finished ~2.5 hours earlier. A polling monitor built on this never fires its completion branch — it just spins forever while the work sits done.

Related run: Running it on a CUDA GPU box (telep-mainframe).

Incident 2 — self-kill mid-script (exit 143)

ssh host 'incus exec c -- bash -c "pkill -f \"openvpn --config /etc/openvpn/tt/farm.conf\"; ..."'

The pkill matched the wrapping bash -c itself and killed its own shell. The command aborted with exit code 143 (128 + 15, SIGTERM), and the remaining steps — a symlink and a systemctl enable — were silently never executed. Nothing in the output said “I killed myself”; it just stopped.

Fixes

Safer patterns

# 1. Best: act on a PID you captured, not on a name
pid=$(pgrep -f "[m]arker_single" | head -1)
kill "$pid"
 
# 2. Bracket trick — breaks the literal self-match
pgrep -f "[m]arker_single"
 
# 3. Exclude self explicitly
pgrep -f pattern | grep -v "^$$\$"
 
# 4. Only match processes older than the current one
pkill -f pattern --older 5
  • For “is process X done?”, prefer a real artifact over process-name polling: the output file appearing, a sentinel file, a recorded EXIT=$?, or systemctl is-active on a unit.
  • After any pkill inside a multi-step script, verify the later steps actually ran. A self-kill aborts silently and — if you only check the final state much later — looks indistinguishable from success.