Skip to content

Commit d73a822

Browse files
committed
fix: fail a SLURM job the scheduler marked FAILED, whatever its exit code
monitor_slurm_job.sh decided pass/fail from SLURM's ExitCode alone and ignored the job state it had already queried and printed. SLURM intermittently reports State=FAILED alongside ExitCode=0:0, and on those jobs the log reads: [20:05:16] Job 13132145 reached terminal state: FAILED Job 13132145 completed successfully The GitHub job then went green with the test failures still in its log. Five of nine "successful" Phoenix gpu-acc runs on master were hiding 23 failing tests this way, which is long enough for a real regression to go unnoticed and makes a green self-hosted job worthless as evidence. run_monitored_slurm_job.sh already re-checks the state via sacct and requires COMPLETED *and* 0:0 -- but only when the monitor exits non-zero, so nothing verified the state on the success path. That asymmetry is the bug; this makes both paths agree. The guard is deliberately conservative: if no terminal state was recorded it falls back to the previous exit-code-only behaviour, so it cannot invent new red CI. Tests: the first case reproduces the false green (the monitor returned 0 where 1 was expected) and the second pins the other direction so a genuinely COMPLETED job still passes. test_monitor_ci_summary.py's stub reported FAILED for every job, including the one named "a successful job" -- a combination SLURM does not produce for a clean run, and only harmless while the state was being ignored. Its state now tracks the exit code, and any test that wants the pathological pairing asks for it explicitly. Claude-Session: https://claude.ai/code/session_017zrZooJPhZtZYgg9fJiYhg
1 parent ec783a8 commit d73a822

3 files changed

Lines changed: 43 additions & 6 deletions

File tree

.github/scripts/monitor_slurm_job.sh

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ while true; do
208208
fi
209209

210210
if is_terminal_state "$state"; then
211+
final_state="$state"
211212
echo "[$(date +%H:%M:%S)] Job $job_id reached terminal state: $state"
212213
break
213214
fi
@@ -308,16 +309,26 @@ case "$exit_code" in
308309
;;
309310
esac
310311

311-
# Check if job succeeded
312-
if [ "$exit_code" != "0:0" ]; then
313-
echo "ERROR: Job $job_id failed with exit code $exit_code"
312+
# Check if job succeeded.
313+
#
314+
# Both the recorded state and the exit code have to agree. SLURM intermittently
315+
# reports State=FAILED alongside ExitCode=0:0 -- on Phoenix the same job that
316+
# printed "reached terminal state: FAILED" here then reported "completed
317+
# successfully" and went green with 23 failing tests still in its log. Five of
318+
# nine "successful" gpu-acc runs on master were hiding failures that way, which
319+
# made a green self-hosted job worthless as evidence.
320+
#
321+
# run_monitored_slurm_job.sh already re-checks the state via sacct, but only
322+
# when this script exits non-zero, so nothing verified it on the success path.
323+
if [ "$exit_code" != "0:0" ] || { [ -n "${final_state:-}" ] && [ "$final_state" != "COMPLETED" ]; }; then
324+
echo "ERROR: Job $job_id failed (state=${final_state:-unknown}, exit code $exit_code)"
314325
# A GPU memory fault explains itself in a block the test harness prints; lift
315326
# it onto the summary page so the faulting kernel and source line are visible
316327
# without opening the log at all.
317328
if grep -q 'GPU fault summary' "$output_file" 2>/dev/null; then
318329
ci_summary "### GPU memory fault\n\n\`\`\`\n$(grep -A6 'GPU fault summary' "$output_file" | head -8 | sed 's/`/'"'"'/g')\n\`\`\`\n"
319330
else
320-
ci_summary "### Job \`$job_id\` failed (exit $exit_code)\n\n\`\`\`\n$(tail -n 15 "$output_file" | sed 's/`/'"'"'/g')\n\`\`\`\n"
331+
ci_summary "### Job \`$job_id\` failed (state ${final_state:-unknown}, exit $exit_code)\n\n\`\`\`\n$(tail -n 15 "$output_file" | sed 's/`/'"'"'/g')\n\`\`\`\n"
321332
fi
322333
exit 1
323334
fi

toolchain/mfc/test_monitor_ci_summary.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,20 @@ def slurm(tmp_path):
3131
binz = tmp_path / "bin"
3232
binz.mkdir()
3333

34-
def configure(exit_code, output):
34+
# The reported state has to track the exit code: the monitor now requires both
35+
# to say the job succeeded, so a stub that always says FAILED would describe a
36+
# job SLURM never produces for a clean run.
37+
def configure(exit_code, output, state=None):
38+
if state is None:
39+
state = "COMPLETED" if exit_code == "0:0" else "FAILED"
40+
3541
def exe(name, body):
3642
path = binz / name
3743
path.write_text(body)
3844
path.chmod(path.stat().st_mode | stat.S_IEXEC)
3945

4046
exe("squeue", "#!/bin/bash\nexit 0\n")
41-
exe("sacct", f'#!/bin/bash\nfor a in "$@"; do [ "$a" = "--format=ExitCode" ] && {{ echo "{exit_code}"; exit 0; }}; done\necho FAILED\n')
47+
exe("sacct", f'#!/bin/bash\nfor a in "$@"; do [ "$a" = "--format=ExitCode" ] && {{ echo "{exit_code}"; exit 0; }}; done\necho "{state}"\n')
4248
exe("scontrol", f'#!/bin/bash\necho "ExitCode={exit_code}"\n')
4349
exe("scancel", "#!/bin/bash\nexit 0\n")
4450

toolchain/mfc/test_monitor_exit_codes.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,23 @@ def test_the_runner_relays_the_infrastructure_exit_code(tmp_path, monitor_exit):
9191
timeout=180,
9292
)
9393
assert result.returncode == monitor_exit
94+
95+
96+
def test_monitor_fails_a_job_slurm_marked_failed_despite_a_zero_exit_code(slurm):
97+
"""A terminal state of FAILED is a failure even when ExitCode reads 0:0.
98+
99+
SLURM reports that combination intermittently on Phoenix. Deciding purely on
100+
the exit code turned those jobs green with the test failures still in the
101+
log -- 5 of 9 "successful" gpu-acc runs on master were hiding 23 failing
102+
tests this way.
103+
"""
104+
tmp_path, binz, configure = slurm
105+
out = configure("0:0", state="FAILED")
106+
assert run_script(tmp_path, binz, "monitor_slurm_job.sh", "1234", str(out)).returncode == 1
107+
108+
109+
def test_monitor_still_passes_a_genuinely_completed_job(slurm):
110+
"""The guard above must not turn healthy jobs red."""
111+
tmp_path, binz, configure = slurm
112+
out = configure("0:0", state="COMPLETED")
113+
assert run_script(tmp_path, binz, "monitor_slurm_job.sh", "1234", str(out)).returncode == 0

0 commit comments

Comments
 (0)