Eureka/tools/wait-on.sh
Eureka d9e45c8740 wait-on: delete WAIT_FROM; reused log paths are forbidden instead
Truncating writers (`>`, Unity -logFile) rewrite below any offset, so an offset
turned a stale success into a silent hang. The option, the scan branch, its docs
and its fixtures are gone; the header states the fresh-log-path rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:21:58 +02:00

92 lines
4.4 KiB
Shell
Executable file

#!/usr/bin/env bash
# One general waiter, so no agent hand-rolls a poll loop. Same contract as
# `ygg-verify.sh wait`: one call blocks at most 270 s (WAIT_MAX can only lower
# that), checks locally every 2 s (no model turn), and exits:
# 0 done; prints a bounded tail (at most 60 lines) of the log when one is given
# 99 still going; prints `RUNNING <n>s (...)`: call it again
# 98 the thing waited on is gone with no completion evidence
# 3 the done regex is not a valid ERE (usage error, nothing was waited on)
# 2 bad usage
# Call it with a Bash tool `timeout` of at least 280000 ms.
#
# Forms:
# wait-on.sh pid <pid> [<log>] process gone: prints `GONE (no completion evidence)`, exit 0
# wait-on.sh log <log> <done-regex> done when a log line matches the ERE
# wait-on.sh file <path> done when the file exists and is non-empty
# `log` accepts WAIT_PID=<pid>: if that process dies and the regex never matched
# (checked once more after the death), exit 98. Without WAIT_PID a log that does
# not exist after 30 s is exit 98 (never started). `pid` cannot tell a finished
# process from one that never existed or crashed: exit 0 there means only "gone",
# never "succeeded". Prefer `log <log> <regex>` with WAIT_PID whenever the job
# writes a done line.
#
# Log paths: every job gets a fresh log path, never one a previous job wrote. The
# waiter greps the whole file, so an old done line reads as success at once. An
# offset cannot fix it: truncating writers (`>`, Unity -logFile) rewrite below it.
#
# Known limitations (Soul pass 1, PR #7; not fixed):
# F5 WAIT_PID naming a launcher that exits while its child keeps writing gives a
# false 98; pass the pid that writes the log, or none.
# F6 `file` is done at the first byte; a writer that is still writing is not
# waited out. Have the job write the file last (rename into place).
# F7 `pid <pid> <log>` prints a tail only; the `log` form greps the
# whole log each 2 s, which is slow on multi-GB logs.
#
# PIDs: on Git Bash a Windows PID (PowerShell Start-Process .Id, a task manager
# number) is seen by `tasklist`, but `kill -0` does not know it; Git Bash's own
# `$!` is known to `kill -0` and also to `tasklist`'s WINPID only by luck. Alive
# means either check says so. Linux uses `kill -0`.
set -uo pipefail
mode=${1:-}; cap=${WAIT_MAX:-270}; [ "$cap" -le 270 ] || cap=270
t0=$(date +%s)
usage() { echo "usage: wait-on.sh pid <pid> [<log>] | log <log> <done-regex> | file <path>" >&2; exit 2; }
alive() {
local p=$1
case "$p" in ''|*[!0-9]*) return 1 ;; esac
kill -0 "$p" 2>/dev/null && return 0
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*)
tasklist //FI "PID eq $p" //NH 2>/dev/null | tr -d '\r' | grep -qE "[[:space:]]$p[[:space:]]" && return 0 ;;
esac
return 1
}
tail60() { [ -n "${1:-}" ] && [ -f "$1" ] && tail -n 60 "$1" | cut -c1-400; return 0; }
running() { echo "RUNNING $(( $(date +%s) - t0 ))s ($1)"; exit 99; }
elapsed() { echo $(( $(date +%s) - t0 )); }
case "$mode" in
pid)
pid=${2:-}; log=${3:-}; [ -n "$pid" ] || usage
while :; do
if ! alive "$pid"; then echo "GONE (no completion evidence)"; tail60 "$log"; exit 0; fi
[ "$(elapsed)" -lt "$cap" ] || running "pid $pid alive; last log line: $(tail -n 1 "$log" 2>/dev/null | cut -c1-160)"
sleep 2
done ;;
log)
log=${2:-}; re=${3:-}; [ -n "$log" ] && [ -n "$re" ] || usage
wp=${WAIT_PID:-}
rc=0; printf '' | grep -Eq -- "$re" 2>/dev/null || rc=$?
[ "$rc" -le 1 ] || { echo "wait-on: usage - /$re/ is not a valid extended regex" >&2; exit 3; }
while :; do
if [ -f "$log" ] && grep -Eq -- "$re" "$log" 2>/dev/null; then tail60 "$log"; exit 0; fi
if [ -n "$wp" ] && ! alive "$wp"; then
sleep 1
if [ -f "$log" ] && grep -Eq -- "$re" "$log" 2>/dev/null; then tail60 "$log"; exit 0; fi
echo "wait-on: DEAD - pid $wp is gone and /$re/ never matched in $log"; tail60 "$log"; exit 98
fi
if [ -z "$wp" ] && [ ! -f "$log" ] && [ "$(elapsed)" -ge 30 ]; then
echo "wait-on: NOT STARTED - no $log after 30 s"; exit 98
fi
[ "$(elapsed)" -lt "$cap" ] || running "no /$re/ yet; last line: $(tail -n 1 "$log" 2>/dev/null | cut -c1-160)"
sleep 2
done ;;
file)
path=${2:-}; [ -n "$path" ] || usage
while :; do
[ -s "$path" ] && exit 0
[ "$(elapsed)" -lt "$cap" ] || running "$path not there yet"
sleep 2
done ;;
*) usage ;;
esac