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.
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.
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 namepid=$(pgrep -f "[m]arker_single" | head -1)kill "$pid"# 2. Bracket trick — breaks the literal self-matchpgrep -f "[m]arker_single"# 3. Exclude self explicitlypgrep -f pattern | grep -v "^$$\$"# 4. Only match processes older than the current onepkill -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.