From 95324934b6713a13d8397628beea609f6b9740fc Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 1 Jun 2026 14:38:55 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20v1.3=20=E2=80=94=20`stop=20--all`=20to?= =?UTF-8?q?=20sweep=20an=20orphan=20daemon=20(lost=20pidfile)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pyauto-pulse stop` only ever worked via the pidfile, so a daemon whose pidfile was deleted out from under it could not be stopped — `stop` reported "not running" while the process lived on, and a subsequent `live`/`watch` would either be refused or spawn a duplicate. Add `stop --all`: a pgrep-based sweep (matched on the daemon.sh script path) that TERMs every pulse daemon process, escalates to KILL for stragglers, and clears the pidfile — regardless of pidfile state. Plain `stop` now also points the user at `--all` when it finds no pidfile. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 5 ++-- bin/pyauto-pulse | 71 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5f952d8..37ae4c1 100644 --- a/README.md +++ b/README.md @@ -215,8 +215,9 @@ pytest tests/ -v ## Roadmap -- v1.3: `stop --all` (pgrep-based) to recover a daemon whose pidfile was lost; - desktop notification dispatch; stash staleness +- v1.3 (shipped): `stop --all` (pgrep-based) to recover a daemon whose pidfile + was lost +- next: desktop notification dispatch; stash staleness - v2: TUI panel (textual/rich) with hotkey drill-down - v3: cross-machine sync (sqlite); team-shared cache diff --git a/bin/pyauto-pulse b/bin/pyauto-pulse index 38a9cf5..2bc6314 100755 --- a/bin/pyauto-pulse +++ b/bin/pyauto-pulse @@ -76,29 +76,72 @@ cmd_live() { } help_stop() { cat </dev/null || true)" + [[ -z "$pids" ]] && { echo 0; return 0; } + for pid in $pids; do + kill "$pid" 2>/dev/null && killed=$((killed + 1)) + done + # Give them a moment to honour TERM (their EXIT trap clears the pidfile), + # then KILL anything still standing. + sleep 1 + for pid in $pids; do + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + fi + done + echo "$killed" +} + cmd_stop() { - if [[ ! -f "$PULSE_PID_FILE" ]]; then + local all=0 + [[ "${1:-}" == "--all" ]] && all=1 + + local stopped_via_pidfile=0 + if [[ -f "$PULSE_PID_FILE" ]]; then + local pid + pid="$(cat "$PULSE_PID_FILE")" + if [[ -z "$pid" ]]; then + echo "$(c_warn 'empty pidfile')" + rm -f "$PULSE_PID_FILE" + elif ! kill -0 "$pid" 2>/dev/null; then + echo "$(c_warn "pid $pid not running; cleaning up stale pidfile")" + rm -f "$PULSE_PID_FILE" + else + kill "$pid" && { echo "$(c_ok "stopped pid $pid")"; stopped_via_pidfile=1; } + fi + elif [[ "$all" -eq 0 ]]; then echo "$(c_meta 'no pidfile — daemon not running')" - return 0 - fi - local pid - pid="$(cat "$PULSE_PID_FILE")" - if [[ -z "$pid" ]]; then - echo "$(c_warn 'empty pidfile')" - rm -f "$PULSE_PID_FILE" - return 0 + echo "$(c_meta '(use `pyauto-pulse stop --all` to sweep for an orphan daemon)')" fi - if ! kill -0 "$pid" 2>/dev/null; then - echo "$(c_warn "pid $pid not running; cleaning up stale pidfile")" + + if [[ "$all" -eq 1 ]]; then + local killed + killed="$(_pulse_kill_all_daemons)" rm -f "$PULSE_PID_FILE" - return 0 + if [[ "$killed" -gt 0 ]]; then + echo "$(c_ok "swept $killed daemon process(es)")" + elif [[ "$stopped_via_pidfile" -eq 0 ]]; then + echo "$(c_meta 'no daemon processes found')" + fi fi - kill "$pid" && echo "$(c_ok "stopped pid $pid")" } help_tick() { cat <