---
name: pkill-f-matches-the-ssh-command-line-that-carries-it
description: ssh host 'pkill -f <name>; …' kills the remote shell running that very command line — ssh exits 255 with NO output, every later step in the string never runs, and the remote state is unknown (doyle 2026-09-10, #294 gate chain).
metadata:
  type: feedback
---

`ssh reavus@kitsubito 'pkill -TERM -f chain-294.sh; pkill -f member-gate…; rmdir lock; mv log; scp…; relaunch'`
returned exit 255 and nothing else. The remote `bash -c` that runs the string carries the literal
`chain-294.sh` in ITS OWN command line, so the first `pkill -f` matched and killed the shell
executing it. Everything after the `;` never ran: the chain script died (its EXIT trap removed the
lock) but its CHILD member-gate leg kept running as an orphan, the log was not moved, the relaunch
did not happen — and from the local side it looked like "ssh dropped".

**Why:** `-f` matches the whole command line of every process, including the wrapper that is
delivering the kill. The failure is silent (255, no stderr) and leaves a half-applied plan whose
remaining steps you believe ran.

**How to apply:** kill remote work by PID (`pgrep -f '[c]hain-294'` first, then `kill <pid>` — the
bracket trick keeps the pattern from matching itself), or run the kill as its own ssh call and
re-read state with a fresh call before doing anything that assumes it landed. Never chain a
`pkill -f` with the steps that depend on it inside one ssh string. Sibling of
[[stopped-local-ssh-does-not-stop-its-remote-command]] — that one is a kill that does not reach
the remote; this one is a kill that reaches too far.

**Sibling face, doyle 2026-09-10:** `pgrep -f "[m]ember-gate"` inside `ssh h 'if pgrep -f "[m]ember-gate" ...; then echo POOL_BUSY; else nohup bash ~/member-gate-kitsubito-v2.sh ... &; fi'` printed POOL_BUSY on an IDLE box — the bracket trick defeats only the `pgrep` process's own line, and the remote `bash -c` shell that carries the whole compound command ALSO contains the literal script name `member-gate-kitsubito-v2.sh` from the launch arm. A false BUSY costs a launch (a false IDLE would cost a pool collision). Put the check and the launch in SEPARATE ssh calls, or match on a token the launch arm does not contain, and read the matched command lines rather than the exit code.
