feat: codex-run.sh watchdog wrapper + stub tests
This commit is contained in:
1 parent
d0035498fa
commit
fa433da81a
4 files changed
+114
No files matched your search
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# codex-crew: deterministic wrapper around `codex exec` with stall watchdog.
|
||||
#
|
||||
# Usage:
|
||||
# codex-run.sh <job_dir> exec [codex-flags...] [prompt]
|
||||
# codex-run.sh <job_dir> resume <thread_id> [codex-flags...] [prompt]
|
||||
# codex-run.sh <job_dir> review [--base X|--commit Y|--uncommitted] [codex-flags...] [prompt]
|
||||
#
|
||||
# If <job_dir>/prompt.md exists and no positional prompt given, it is fed via stdin.
|
||||
# Review runs against the git repo at the CURRENT WORKING DIRECTORY (no -C support).
|
||||
#
|
||||
# Files written to <job_dir>: events.jsonl last.md err.log thread_id status
|
||||
# status: ok | stalled | error: <reason>. Exit code 0 only on ok.
|
||||
#
|
||||
# Env knobs:
|
||||
# CODEX_STALL_TIMEOUT seconds of event silence before kill (default 600)
|
||||
# CODEX_WATCH_INTERVAL watchdog poll interval seconds (default 15)
|
||||
# CODEX_BIN codex binary (default codex; tests use a stub)
|
||||
set -u
|
||||
|
||||
JOB_DIR=$1; MODE=$2; shift 2
|
||||
mkdir -p "$JOB_DIR"
|
||||
JOB_DIR=$(cd "$JOB_DIR" && pwd)
|
||||
EV=$JOB_DIR/events.jsonl LAST=$JOB_DIR/last.md ERR=$JOB_DIR/err.log
|
||||
STALL=${CODEX_STALL_TIMEOUT:-600}
|
||||
TICK=${CODEX_WATCH_INTERVAL:-15}
|
||||
BIN=${CODEX_BIN:-codex}
|
||||
|
||||
case $MODE in
|
||||
exec) CMD=("$BIN" exec --json --skip-git-repo-check -o "$LAST") ;;
|
||||
resume) TID=$1; shift; printf '%s\n' "$TID" > "$JOB_DIR/thread_id"
|
||||
CMD=("$BIN" exec resume "$TID" --json --skip-git-repo-check -o "$LAST") ;;
|
||||
review) CMD=("$BIN" exec review --json -o "$LAST") ;;
|
||||
*) echo "usage: codex-run.sh <job_dir> exec|resume|review ..." >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
rm -f "$JOB_DIR/status" "$LAST"; : > "$EV"; : > "$ERR"
|
||||
|
||||
if [[ $# -eq 0 && -s $JOB_DIR/prompt.md ]]; then
|
||||
"${CMD[@]}" - < "$JOB_DIR/prompt.md" > "$EV" 2> "$ERR" &
|
||||
else
|
||||
"${CMD[@]}" "$@" < /dev/null > "$EV" 2> "$ERR" &
|
||||
fi
|
||||
PID=$!
|
||||
|
||||
( while kill -0 "$PID" 2>/dev/null; do
|
||||
sleep "$TICK"
|
||||
now=$(date +%s); mt=$(stat -c %Y "$EV" 2>/dev/null || echo "$now")
|
||||
if (( now - mt > STALL )); then
|
||||
echo stalled > "$JOB_DIR/status"
|
||||
kill "$PID" 2>/dev/null; sleep 3; kill -9 "$PID" 2>/dev/null
|
||||
break
|
||||
fi
|
||||
done ) &
|
||||
WATCH=$!
|
||||
|
||||
wait "$PID"; RC=$?
|
||||
kill "$WATCH" 2>/dev/null; wait "$WATCH" 2>/dev/null
|
||||
|
||||
TID_FOUND=$(grep -m1 -o '"thread_id":"[^"]*"' "$EV" | cut -d'"' -f4)
|
||||
[[ -n $TID_FOUND ]] && printf '%s\n' "$TID_FOUND" > "$JOB_DIR/thread_id"
|
||||
|
||||
if [[ -s $JOB_DIR/status ]]; then
|
||||
: # stalled, written by watchdog
|
||||
elif [[ $RC -eq 0 && -s $LAST ]]; then
|
||||
echo ok > "$JOB_DIR/status"
|
||||
else
|
||||
echo "error: exit=$RC $(tail -c 300 "$ERR" | tr '\n' ' ')" > "$JOB_DIR/status"
|
||||
fi
|
||||
|
||||
ST=$(head -n1 "$JOB_DIR/status")
|
||||
echo "status: $ST"
|
||||
echo "thread_id: $(cat "$JOB_DIR/thread_id" 2>/dev/null || echo none)"
|
||||
[[ $ST == ok ]]
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
# fake codex: emits one event then hangs silently
|
||||
echo '{"type":"thread.started","thread_id":"hang-thread-456"}'
|
||||
sleep 300
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# fake codex: emits thread.started, writes last message file, exits 0
|
||||
out=""
|
||||
prev=""
|
||||
for a in "$@"; do [[ $prev == "-o" ]] && out=$a; prev=$a; done
|
||||
echo '{"type":"thread.started","thread_id":"test-thread-123"}'
|
||||
echo '{"type":"turn.completed"}'
|
||||
echo "stub answer" > "$out"
|
||||
exit 0
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
RUN=$HERE/../scripts/codex-run.sh
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# 1. happy path -> status ok, thread_id captured, exit 0
|
||||
CODEX_BIN=$HERE/stub-ok/codex "$RUN" "$TMP/ok" exec "hello"
|
||||
[[ $(cat "$TMP/ok/status") == ok ]]
|
||||
[[ $(cat "$TMP/ok/thread_id") == test-thread-123 ]]
|
||||
[[ $(cat "$TMP/ok/last.md") == "stub answer" ]]
|
||||
|
||||
# 2. stall -> watchdog kills, status stalled, exit nonzero
|
||||
if CODEX_BIN=$HERE/stub-hang/codex CODEX_STALL_TIMEOUT=4 CODEX_WATCH_INTERVAL=2 \
|
||||
"$RUN" "$TMP/hang" exec "hello"; then
|
||||
echo "FAIL: stall run must exit nonzero"; exit 1
|
||||
fi
|
||||
[[ $(cat "$TMP/hang/status") == stalled ]]
|
||||
[[ $(cat "$TMP/hang/thread_id") == hang-thread-456 ]]
|
||||
|
||||
# 3. prompt.md via stdin path
|
||||
mkdir -p "$TMP/stdin"; echo "prompt from file" > "$TMP/stdin/prompt.md"
|
||||
CODEX_BIN=$HERE/stub-ok/codex "$RUN" "$TMP/stdin" exec
|
||||
[[ $(cat "$TMP/stdin/status") == ok ]]
|
||||
|
||||
echo "ALL PASS"
|
||||
Reference in new issue
Block a user