Three CI jobs failed on 2026-08-14/15 with no failing test. All three were runner-agent deaths mid-job. Each cost a 20-40 minute job and, because the failure looks like a code failure, an investigation.
Signature (identical in all three):
- The job log stops mid-stream while the job keeps running for another 15-20 minutes.
- The
test-root step never receives a conclusion; every later step stays pending (.steps[].conclusion == null), including "Upload test logs" — so the test-logs-* artifact is MISSING for exactly the failed job while every sibling job in the same run uploaded one.
- No
TIMEOUT line, and tests were completing normally right up to the last emitted line. CI's nextest profile terminates any test after 2x120s and reports it, so a hung test would have been named.
- GitHub eventually marks the job
failure.
Evidence per occurrence (all on PR #818's head 2917e07, three different machines):
| job |
runner |
duration |
instance state after |
| Host-Root-x64-SnapshotDisabled |
runner-i-0d74c773f1e998a00 |
17:20:53 -> 18:00:57 |
terminated (gone from EC2) |
| Host-Root-x64-SnapshotDisabled |
runner-i-0f0956aaa6475b7c6 |
18:23:44 -> 18:43:46 |
terminated (gone from EC2) |
| Host-Root-arm64-SnapshotDisabled |
runner-i-02b10d79333d05c37 |
23:58:35 -> 00:32:38 |
instance still running, agent status=offline |
The third case is the clearest: the EC2 instance is alive and the GitHub runner agent is simply gone. SSH to it also times out from an allowlisted IP, so the box is not healthy either.
Confirmation it is not the code: the same commit, rerun on a fresh runner, passed 15/15 with every required job present, including the Host-Root-arm64-SnapshotDisabled job that had died.
Why it concentrates on SnapshotDisabled: that configuration cold-boots every VM, making it by far the longest and most VM-heavy job, so it has the widest window to be caught by whatever kills the agent (scale-down/lease reclaim, or the host wedging under load).
Why it matters beyond the lost time: the failure is indistinguishable from a code failure at a glance, and it stops the run before the diagnostic artifact is uploaded — so the evidence needed to tell the two apart is exactly what gets destroyed. This session spent a long investigation, and produced one wrong attribution to a PR, before the runner data settled it.
Suggested directions (autoscaler lives in ~/aws/runner-autoscale.tf):
- Do not reclaim/terminate an instance whose runner reports
busy=true; if a lease must expire, drain rather than kill.
- Emit a distinguishable signal when the agent dies mid-job (a post-job check, or an annotation) so a dead agent never reads as a test failure.
- Consider uploading test logs incrementally, or on a trap, so a killed job still leaves its diagnostics.
Three CI jobs failed on 2026-08-14/15 with no failing test. All three were runner-agent deaths mid-job. Each cost a 20-40 minute job and, because the failure looks like a code failure, an investigation.
Signature (identical in all three):
test-rootstep never receives a conclusion; every later step stayspending(.steps[].conclusion == null), including "Upload test logs" — so thetest-logs-*artifact is MISSING for exactly the failed job while every sibling job in the same run uploaded one.TIMEOUTline, and tests were completing normally right up to the last emitted line. CI's nextest profile terminates any test after 2x120s and reports it, so a hung test would have been named.failure.Evidence per occurrence (all on PR #818's head 2917e07, three different machines):
running, agentstatus=offlineThe third case is the clearest: the EC2 instance is alive and the GitHub runner agent is simply gone. SSH to it also times out from an allowlisted IP, so the box is not healthy either.
Confirmation it is not the code: the same commit, rerun on a fresh runner, passed 15/15 with every required job present, including the Host-Root-arm64-SnapshotDisabled job that had died.
Why it concentrates on SnapshotDisabled: that configuration cold-boots every VM, making it by far the longest and most VM-heavy job, so it has the widest window to be caught by whatever kills the agent (scale-down/lease reclaim, or the host wedging under load).
Why it matters beyond the lost time: the failure is indistinguishable from a code failure at a glance, and it stops the run before the diagnostic artifact is uploaded — so the evidence needed to tell the two apart is exactly what gets destroyed. This session spent a long investigation, and produced one wrong attribution to a PR, before the runner data settled it.
Suggested directions (autoscaler lives in ~/aws/runner-autoscale.tf):
busy=true; if a lease must expire, drain rather than kill.