app
hook
Public Made by Adomby adom
Registry-native cross-agent auto-update hook, plus claude-rc, the keeper for Claude Remote Control. Keeps adom/core apps and skills current from wiki.adom.inc on every Claude Code and Codex prompt, and keeps `claude rc` alive so users can open AI threads on their container from the Claude mobile app or claude.ai/code.
← Commit history
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,