← Commit history

Publish 1.3.9

John Lauer ·236c5b023c ·21d ago ·parent 633d885
4 files changed +58−9
SKILL.md+7
@@ -152,6 +152,13 @@ used. Detection is `ADOM_DESKTOP_MODE=local` plus a live crond.  ## Facts worth knowing before you debug it +- Every restart used to leave its old wrapper shell and `sleep infinity` behind (the stdin+  holder never learns the server died), and the stop code meant to reap them used a+  `pgrep` pattern with a bare `{` that this procps rejects as invalid, so it never matched+  (1.3.9, found on AdomLapper and this container: 4 and 11 leftover groups). Since 1.3.9+  every launch, stop and restart kills whole process groups (`kill -- -PGID`) and reaps+  leftovers, sparing only the live server's group. `claude-rc status` should show one group.+ - `claude rc` skips its "Enable Remote Control? (y/n)" prompt when stdin is not a TTY, but   it still READS stdin (it waits for a keypress to show the QR code) and exits on EOF. So the   keeper launches it with a stdin that never closes (`sleep infinity | exec claude rc ...`),
claude-rc+49−7
@@ -71,10 +71,15 @@ running_pid() { pgrep -f "$PAT" | head -1; } # TERM, wait up to 8s for a graceful exit, then KILL. `claude rc` takes a moment to # tear down its relay connection, so a bare pkill + immediate pgrep still sees it. stop_server() {-  pkill -f "$PAT" 2>/dev/null || return 0-  pkill -f '^bash -c { printf "y.n"; sleep infinity; } \| exec claude rc' 2>/dev/null || true-  local i; for i in 1 2 3 4 5 6 7 8; do [ -z "$(running_pid)" ] && return 0; sleep 1; done+  # Kill the server's whole group (wrapper, subshell, its sleep), not pids: killing the wrapper+  # by pid orphaned its `sleep infinity` forever (1.3.9).+  local pid pg; pid="$(running_pid)"+  [ -n "$pid" ] && pg="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"+  pkill -f "$PAT" 2>/dev/null || { reap_orphans; return 0; }+  [[ "${pg:-}" =~ ^[0-9]+$ ]] && [ "$pg" -gt 1 ] && kill -- -"$pg" 2>/dev/null+  local i; for i in 1 2 3 4 5 6 7 8; do [ -z "$(running_pid)" ] && { reap_orphans; return 0; }; sleep 1; done   pkill -9 -f "$PAT" 2>/dev/null || true; sleep 1+  reap_orphans }  # Prerequisites `claude rc` needs: the CLI, a logged-in account, and the workspace@@ -162,7 +167,40 @@ rotate_log() {   [ -f "$LOG" ] && [ "$(stat -c %s "$LOG" 2>/dev/null || echo 0)" -gt 5000000 ] && mv "$LOG" "$LOG.1"   return 0 }+# REAP LEFTOVER SERVER GROUPS (John 2026-09-29, AdomLapper: four restarts in a day left four+# wrapper shells, each still holding its `sleep infinity`). The server runs as+# `{ printf "y\n"; sleep infinity; } | exec claude rc` in its own session (setsid). When claude rc+# dies, the sleep half never learns it (it never writes, so it never sees SIGPIPE), and the wrapper+# waits on it forever. Before a launch, kill every such group whole, sparing the group of a server+# that is actually alive. Group kill (`kill -- -PGID`) takes the wrapper, the subshell and the sleep+# together, which a pid kill cannot: the sleep would just be reparented and keep running.+reap_orphans() {+  local live_pg="" me_pg pg pid+  pid="$(running_pid)"+  [ -n "$pid" ] && live_pg="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"+  me_pg="$(ps -o pgid= -p $$ 2>/dev/null | tr -d ' ')"+  for pg in $(pgrep -f '^bash -c \{ printf "y.n"; sleep infinity; \} \| exec claude rc' 2>/dev/null \+              | xargs -r -n1 ps -o pgid= -p 2>/dev/null | tr -d ' ' | sort -u); do+    [[ "$pg" =~ ^[0-9]+$ ]] && [ "$pg" -gt 1 ] || continue+    [ "$pg" = "$live_pg" ] || [ "$pg" = "$me_pg" ] && continue+    kill -- -"$pg" 2>/dev/null || true+  done+  # Older versions killed the wrapper by pid and left its `sleep infinity` orphaned alone in the+  # dead group. Reap those too, narrowly: argv exactly "sleep infinity", reparented to init, its+  # group leader gone, and running in our project dir (where launch starts the server).+  for pid in $(pgrep -x sleep 2>/dev/null); do+    [ "$(tr '\0' ' ' < /proc/"$pid"/cmdline 2>/dev/null)" = "sleep infinity " ] || continue+    [ "$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')" = "1" ] || continue+    pg="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"+    [ -n "$pg" ] && [ "$pg" != "$pid" ] && [ "$pg" != "$live_pg" ] || continue+    [ -e /proc/"$pg" ] && continue+    [ "$(readlink /proc/"$pid"/cwd 2>/dev/null)" = "$(cd "$PROJECT_DIR" 2>/dev/null && pwd -P)" ] || continue+    kill "$pid" 2>/dev/null || true+  done+}+ spawn_server() {+  reap_orphans   # STDIN MUST STAY OPEN. `claude rc` is a TUI that waits on a keypress ("space to show QR   # code"); with </dev/null it connects, reads EOF, and exits within a second (the log shows   # "Connected" five times, then the crash-loop guard marks it failed). A pipe that never@@ -234,10 +272,14 @@ server_identity_stale() { restart_server() {   local pid="$1"   [ -n "$pid" ] || return 0-  kill "$pid" 2>/dev/null || true-  # the `printf y | claude rc` keeper shares the group; take its parent too when it is ours-  local ppid; ppid="$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')"-  if [ -n "$ppid" ] && ps -o cmd= -p "$ppid" 2>/dev/null | grep -q "claude r[c] --name"; then kill "$ppid" 2>/dev/null || true; fi+  # The `printf y | claude rc` keeper shares the server's group: kill the group whole so its+  # wrapper and `sleep infinity` go with it (1.3.9; a pid kill left the sleep behind).+  local pg; pg="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"+  if [[ "$pg" =~ ^[0-9]+$ ]] && [ "$pg" -gt 1 ] && [ "$pg" != "$(ps -o pgid= -p $$ 2>/dev/null | tr -d ' ')" ]; then+    kill -- -"$pg" 2>/dev/null || true+  else+    kill "$pid" 2>/dev/null || true+  fi   sleep 3   launch }
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "hook",-  "version": "1.3.8",+  "version": "1.3.9",   "type": "app",   "org": "adom",   "bundled": true,
page.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "hook",-  "version": "1.3.8",+  "version": "1.3.9",   "type": "app",   "org": "adom",   "bundled": true,