Skip to content

Commit 3013d26

Browse files
authored
fix(test-classifier): export CI=true for OBSERVED suite runs (#59)
vitest browser mode (and other watch-defaulting runners) hung under the headless agent shell: they printed a banner, waited for an interactive browser session that never attached, collected zero tests, and the run fell back to INFERRED. Set CI=true in invoke_ai when AI_RUN_SUITE=1 so the runner does a single, headless, non-watch pass (vitest reads CI to force headless + run-once; jest and others honor it too). The AI process and the test runner it spawns inherit it. Subtlety handled: should_stream() is gated on CI != true, so the streaming decision is now made in invoke_ai BEFORE exporting CI (stashed in AI_REVIEW_DO_STREAM and read by invoke_claude) — otherwise OBSERVED runs would silently lose live progress. The export lives in the `$(...)` subshell, so the dispatcher's own color/stream state is untouched. Verified end-to-end against a vitest-browser-mode repo: OBSERVED now runs the suite (2 failed / 3 passed, stable across two runs) instead of hanging to INFERRED. Runner-agnostic; applies to claude/codex/copilot via invoke_ai.
1 parent 475059b commit 3013d26

1 file changed

Lines changed: 30 additions & 3 deletions

File tree

testing/classifier/.skills/_lib/ai-classifier-dispatch.sh

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,10 @@ ai_review::invoke_claude() {
423423
# NDJSON events that stream_split narrates to STDERR while forwarding only the
424424
# final result text to STDOUT — so the captured stdout (and its marker/JSON
425425
# parse) is identical to the plain `-p` path. CI keeps the silent, clean path.
426-
local stream=0
427-
if ai_review::should_stream; then
428-
stream=1
426+
# Streaming decision is made in invoke_ai (BEFORE it exports CI=true for the
427+
# suite run, since should_stream is gated on CI). We just read it here.
428+
local stream="${AI_REVIEW_DO_STREAM:-0}"
429+
if (( stream == 1 )); then
429430
ai_review::info "Streaming the agent's steps below (set AI_REVIEW_STREAM=0 to silence)…" >&2
430431
fi
431432

@@ -495,6 +496,32 @@ ai_review::invoke_copilot() {
495496
}
496497

497498
ai_review::invoke_ai() {
499+
# In OBSERVED mode the agent runs the repo's test suite. Many JS runners
500+
# (vitest browser mode, jest) default to interactive WATCH mode when stdin
501+
# isn't a TTY-less CI context — under our headless agent shell they print a
502+
# banner and then HANG waiting for a browser session that never attaches, so
503+
# the suite "runs" but collects zero tests. Exporting CI=true makes those
504+
# runners do a single, headless, non-watch run instead (vitest reads CI to
505+
# force headless + run-once; jest/others honor it similarly). The agent
506+
# subprocess and the test runner it spawns inherit this.
507+
#
508+
# Safe to export here: invoke_ai is called via `$(...)`, which runs it in a
509+
# SUBSHELL, so this never reaches the dispatcher's own color state. It only
510+
# affects the AI process and its children.
511+
#
512+
# ORDER MATTERS: should_stream() is gated on CI != true, so we must decide
513+
# whether to stream BEFORE exporting CI — otherwise OBSERVED runs (which set
514+
# CI=true) would silently lose live progress. Stash the decision in
515+
# AI_REVIEW_DO_STREAM for the tool functions to read.
516+
if ai_review::should_stream; then
517+
AI_REVIEW_DO_STREAM=1
518+
else
519+
AI_REVIEW_DO_STREAM=0
520+
fi
521+
if (( AI_RUN_SUITE == 1 )); then
522+
export CI=true
523+
fi
524+
498525
case "${AI_REVIEW_TOOL_RESOLVED}" in
499526
claude) ai_review::invoke_claude ;;
500527
codex) ai_review::invoke_codex ;;

0 commit comments

Comments
 (0)