fix(uno-q): make supervisor survive a reboot + recover actually launch it

A board power-cycle exposed two bugs that left the node dead after boot:

- recover-uno-q.sh step 3 ran `pgrep -f zeroclaw-supervisor`, which matches
  the pgrep's OWN shell (its args contain the supervisor path) — so it always
  concluded "already running" and never launched the supervisor. Bracket the
  pattern (`[z]eroclaw-supervisor.sh`) so only the real process matches.

- The supervisor's single-instance lock only checked that the locked pid was
  alive. After a reboot the stale pid can be recycled by an unrelated process,
  falsely blocking startup. Now require the live pid's /proc/<pid>/cmdline to
  actually be a supervisor before deferring — otherwise treat the lock as stale.

- Also export a full PATH in the supervisor for cron's minimal @reboot env.

Validated on hardware: discriminator matches a real supervisor and rejects
init's pid; fixed recover detects the running supervisor without spawning a
duplicate; board comes back healthy (gateway + llama up).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-04 16:36:01 -07:00
co-authored by Claude Opus 4.8
parent 6cfce07f77
commit 21fc93556e
2 changed files with 16 additions and 5 deletions
+3 -1
View File
@@ -36,7 +36,9 @@ echo " forward :8080 · reverse :$SHIM_PORT"
echo "==> [3/5] ensure on-board supervisor is running" echo "==> [3/5] ensure on-board supervisor is running"
if a shell "test -x $SUPERVISOR" 2>/dev/null; then if a shell "test -x $SUPERVISOR" 2>/dev/null; then
a shell "pgrep -f zeroclaw-supervisor >/dev/null 2>&1 \ # Bracket the pattern so the pgrep's own shell (whose args contain the
# supervisor path) doesn't self-match and skip the launch.
a shell "pgrep -f '[z]eroclaw-supervisor.sh' >/dev/null 2>&1 \
|| (setsid nohup $SUPERVISOR >/dev/null 2>&1 < /dev/null &)" >/dev/null 2>&1 || (setsid nohup $SUPERVISOR >/dev/null 2>&1 < /dev/null &)" >/dev/null 2>&1
echo " supervisor ensured (restarts llama + daemon on death/wedge)" echo " supervisor ensured (restarts llama + daemon on death/wedge)"
else else
+12 -3
View File
@@ -16,6 +16,9 @@
# Boot persist: crontab -l | { cat; echo '@reboot /home/arduino/zeroclaw-supervisor.sh'; } | crontab - # Boot persist: crontab -l | { cat; echo '@reboot /home/arduino/zeroclaw-supervisor.sh'; } | crontab -
set -u set -u
# cron's @reboot env is minimal — guarantee the tools we shell out to are found.
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}
HOME_DIR=/home/arduino HOME_DIR=/home/arduino
LLAMA_DIR="$HOME_DIR/llama" LLAMA_DIR="$HOME_DIR/llama"
MODEL="$HOME_DIR/models/qwen.gguf" MODEL="$HOME_DIR/models/qwen.gguf"
@@ -30,10 +33,16 @@ log() { echo "[$(date '+%F %T')] $*" >> "$LOG"; }
now() { date +%s; } now() { date +%s; }
healthy() { curl -sf --max-time 4 "http://127.0.0.1:$1/health" >/dev/null 2>&1; } healthy() { curl -sf --max-time 4 "http://127.0.0.1:$1/health" >/dev/null 2>&1; }
# single-instance guard (no flock dependency) # single-instance guard (no flock dependency). Pid-reuse-safe: a stale lock
if [ -f "$LOCK" ] && kill -0 "$(cat "$LOCK" 2>/dev/null)" 2>/dev/null; then # whose pid was recycled by an unrelated process (common right after a reboot)
log "supervisor already running (pid $(cat "$LOCK")) — exiting" # must NOT block startup — so require the live pid to actually be a supervisor.
if [ -f "$LOCK" ]; then
OLDPID=$(cat "$LOCK" 2>/dev/null)
if [ -n "$OLDPID" ] && kill -0 "$OLDPID" 2>/dev/null \
&& grep -qa zeroclaw-supervisor "/proc/$OLDPID/cmdline" 2>/dev/null; then
log "supervisor already running (pid $OLDPID) — exiting"
exit 0 exit 0
fi
fi fi
echo $$ > "$LOCK" echo $$ > "$LOCK"
trap 'rm -f "$LOCK"' EXIT trap 'rm -f "$LOCK"' EXIT