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>
92 lines
4.4 KiB
Shell
Executable file
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
|