I run my GitHub Actions jobs on my own laptop: a self-hosted runner in Docker, one fresh container per job, several jobs in parallel. It saves Actions minutes and, after putting the tools in the runner image, it is fast. Then the laptop started to reset itself: four times in three days, no blue screen, no warning. The screen went black and Windows booted again.
After three changes it has now run two full days without a reset. This post covers how I narrowed it down and what I changed.
What Windows told me (not much)
After every reset the event log showed the same two events and nothing else:
| Event | Meaning |
|---|---|
| Kernel-Power 41 | The system rebooted without shutting down cleanly |
| EventLog 6008 | The previous shutdown was unexpected |
More telling was what was missing:
BugcheckCode = 0: no bug check, so Windows did not crash.PowerButtonTimestamp = 0: nobody held the power button.- No WHEA errors (the hardware error reports).
- No minidump, although small dumps were enabled.
When Windows crashes, it writes a dump. When the power is cut or the firmware resets the machine, it has no chance to. So this was a hard reset below Windows: power delivery, firmware or hardware, not a driver or software crash.
The pattern fit that: the resets came under load. One came four minutes after a pull request's checks started on the runner, another during a heavy local test run (a full test suite plus a headless Chromium) on top of the runner.
What I ruled out
- BIOS and firmware: already the latest version.
- The dock and its power adapter: I had reboots on both (dock and separate adapter).
- The battery:
powercfg /batteryreportshowed 97 % of design capacity after 120 cycles. Healthy.
That left the load itself: too many cores at full boost at the same time, for too long.
The three changes
1. CPU and memory limits per job container
Without a limit, every job container may use every core and all memory. A few jobs at once, each running npm ci, a test suite or a browser, put the whole machine at 100 %. Docker can cap each container:
docker run --cpus 4 --memory 3g --memory-swap 3g ...
--memory-swap equal to --memory means no extra swap: a job that needs more than 3 GB stops with "out of memory" instead of slowing the whole laptop down. In my runner start script these are parameters with these values as defaults.
2. Fewer jobs in parallel: 2 instead of 4
The runner script starts a number of runner slots, each picking up one job at a time. I went from 4 to 2. A pull request's checks take a little longer, but the peak load is about half.
3. Turbo boost off: maximum processor state 99 %
The odd one. In Windows' power options, Processor power management → Maximum processor state set to 99 % instead of 100 % disables turbo boost: the CPU stays at its base clock. That lowers the peak power draw and the heat considerably, for a modest loss in speed.
Through the GUI: Control Panel → Power Options → Change plan settings → Change advanced power settings → Processor power management → Maximum processor state, for "Plugged in" (and "On battery" if you like).
Or from an administrator prompt, for the active plan:
powercfg /setacvalueindex SCHEME_CURRENT SUB_PROCESSOR PROCTHROTTLEMAX 99
powercfg /setdcvalueindex SCHEME_CURRENT SUB_PROCESSOR PROCTHROTTLEMAX 99
powercfg /setactive SCHEME_CURRENT
Set it back to 100 to undo it.
Result
Before: four resets in three days. After: two full days of normal runner work without a single reset.
To be honest about what that proves: I made the changes together, so I can't tell which one did it, and two days is a good sign, not proof. All three point the same way, though: lower the peak power draw and heat. My guess is that the laptop's power delivery or cooling can't keep up with all cores at full boost for minutes on end, and the firmware resets the machine to protect itself. I might try again later with -Parallel 4.
If this happens to you
- Check the event log for Kernel-Power 41 with
BugcheckCode = 0and no dump. That points below Windows; stop hunting drivers. - Rule out the cheap things: firmware up to date, a different charger, a battery report.
- Cap your job containers (
--cpus,--memory) and run fewer jobs in parallel. - Try maximum processor state 99 %. It's free, it's reversible, and it costs less speed than you would expect.
- If it still resets: log temperatures and power with a tool like HWiNFO to a CSV file (the last rows survive the reset), run a memory test, and then it's a case for the manufacturer's service.