From 843c47338234093dde52f87fbaa9b83a7f356e2a Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Tue, 9 Jun 2026 17:27:30 -0700 Subject: [PATCH] fix(brainstorm-server): tie stop-server PID check to the session's port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The node+server.cjs command match (from the adversarial review) still matched any unrelated node process running a file named server.cjs. When we recorded the bound port (state/server-info) and lsof is available, additionally require the PID to be the process actually LISTENING on this session's port — which rules out a different project's server.cjs / editor task runner that recycled the stale PID. Falls back to the command match when the port or lsof isn't available. Test: a 'node server.cjs' process not listening on the recorded port is spared. Refs #1703 --- skills/brainstorming/scripts/stop-server.sh | 15 +++++++++++++- tests/brainstorm-server/stop-server.test.sh | 22 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/skills/brainstorming/scripts/stop-server.sh b/skills/brainstorming/scripts/stop-server.sh index 7d99d4b5..082d065c 100755 --- a/skills/brainstorming/scripts/stop-server.sh +++ b/skills/brainstorming/scripts/stop-server.sh @@ -21,9 +21,22 @@ PID_FILE="${STATE_DIR}/server.pid" is_brainstorm_server() { kill -0 "$1" 2>/dev/null || return 1 case "$(ps -p "$1" -o command= 2>/dev/null)" in - *node*server.cjs*) return 0 ;; + *node*server.cjs*) ;; *) return 1 ;; esac + # Stronger check: if we recorded the bound port and lsof is available, require + # the PID to be the process actually LISTENING on this session's port. This + # rules out an unrelated `node ... server.cjs` (another project, an editor task + # runner, a different session) that happened to recycle the stale PID. + local info="${STATE_DIR}/server-info" + if [[ -f "$info" ]] && command -v lsof >/dev/null 2>&1; then + local port + port=$(sed -n 's/.*"port":\([0-9][0-9]*\).*/\1/p' "$info" | head -1) + if [[ -n "$port" ]]; then + [[ "$(lsof -nP -iTCP:"$port" -sTCP:LISTEN -t 2>/dev/null | head -1)" == "$1" ]] || return 1 + fi + fi + return 0 } if [[ -f "$PID_FILE" ]]; then diff --git a/tests/brainstorm-server/stop-server.test.sh b/tests/brainstorm-server/stop-server.test.sh index 76244202..1c979767 100755 --- a/tests/brainstorm-server/stop-server.test.sh +++ b/tests/brainstorm-server/stop-server.test.sh @@ -60,5 +60,27 @@ case "$OUT" in esac rm -rf "$SESS" +# --- Test 4: a `node server.cjs` impostor NOT listening on our port is spared --- +if command -v lsof > /dev/null 2>&1; then + SESS="$(mktemp -d)"; mkdir -p "$SESS/state" + echo '{"type":"server-started","port":3499}' > "$SESS/state/server-info" # nothing listens on 3499 + ( exec -a "node server.cjs" sleep 600 ) & + IMPOSTOR=$! + echo "$IMPOSTOR" > "$SESS/state/server.pid" + OUT="$("$STOP" "$SESS")" + if kill -0 "$IMPOSTOR" 2>/dev/null; then + case "$OUT" in + *stale_pid*) ok "a node server.cjs not listening on our port is left alone" ;; + *) bad "impostor survived but status was not stale_pid" "$OUT" ;; + esac + else + bad "killed a node server.cjs that was NOT on our recorded port" "$OUT" + fi + kill -9 "$IMPOSTOR" 2>/dev/null + rm -rf "$SESS" +else + echo " SKIP: lsof unavailable — port cross-check test" +fi + echo "--- Results: $PASS passed, $FAIL failed ---" [ "$FAIL" -eq 0 ] || exit 1