The runner has received a shutdown signal
A log line saying the runner has received a shutdown signal in GitHub Actions comes from the runner agent itself, not from your build: the worker executing your job was told to stop. Something took the agent or the machine under it away while the job was still running, and the job is reported as failed even though nothing in it went wrong.

What this error means
The job fails with an error annotation reading "The runner has received a shutdown signal. This can happen when the runner service is stopped, or a manually started runner is canceled." The step output usually stops mid-sentence, because the process tree went away with the agent that owned it. There is no test failure and no exit code from your program. On a self-hosted fleet it arrives in batches, because whatever stopped one agent stopped several.
The runner has received a shutdown signal. This can happen when the runner service is stopped, or a manually started runner is canceled.Where the sentence comes from
This is not a GitHub service message but a string in the runner agent. The agent registers a callback for its own shutdown token, and when that fires mid-job it writes an error onto the job. The wording in src/Runner.Worker/JobRunner.cs is exactly what you see, chosen for the shutdown reason the agent was given.
A sibling message sits on the same switch: "Operating system is shutting down for computer" followed by the machine name. If you see that one, the host is going down rather than the service being stopped, and the investigation starts with the machine.
The scale is easy to check: a GitHub issue search for the sentence in:body returned 1,207 results when we read it on 20 September 2026, across the runner repository, the Actions Runner Controller tracker and hundreds of ordinary projects. There is no reproduction on this page, because the message is the agent reporting its own shutdown and nothing we could run on a runner would produce it.
Common causes
The runner service was stopped or restarted under a running job
A configuration management run, a package upgrade, or an agent auto-update that restarts the service. The runner cannot finish the job first, so it writes this line and stops. On an automated fleet this is the first thing to check, because the timing usually correlates exactly.
The host or pod was drained, upgraded or rebooted
On Kubernetes, a node upgrade or a scale-in evicts the runner pod while the job is still going. On virtual machines, an unattended upgrade with a reboot does the same. The cluster or the host is behaving correctly; it simply does not know a job is in flight.
A spot or preemptible machine was reclaimed
Preemptible capacity is taken back on the provider's schedule with a short notice window, and the shutdown reaches the agent as this message. In our experience this is the version that produces clusters of failures at the same time of day.
A manually started runner was stopped
The message names this case explicitly: a runner started by hand and then interrupted reports the same line. It is the easiest cause to confirm and worth ruling out first on a small fleet.
How to fix it
Confirm it was the agent, not your step
- Check whether the job has an exit code of its own. This failure has none.
- Look at the machine or pod that ran the job, not the job log, and line up the timestamps against restarts, evictions and reboots.
- Check whether several jobs failed within the same minute, which points at a host or a node rather than at a workflow.
Stop draining nodes that are holding running jobs
A grace period longer than a typical job is not practical, so narrow the window instead: a pod disruption budget keeps the scheduler from evicting every runner at once, a longer termination grace period buys short jobs time to finish, and moving upgrades outside peak CI hours removes most of what is left.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: arc-runners
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: gha-runner-scale-setKeep long jobs off preemptible capacity
Spot capacity is a good trade for short jobs and a bad one for a forty minute suite, because the chance of being reclaimed grows with runtime. Split long jobs onto on-demand capacity and leave the fast ones where they are.
jobs:
unit:
runs-on: [self-hosted, spot] # short, cheap, interruptible
integration:
runs-on: [self-hosted, on-demand] # long, and worth finishingTake the long-lived agent out of the picture
Where the agent is a service you maintain, every restart is a chance to hit this. One-job machines remove the restart entirely, whether you build that with ephemeral self-hosted runners or use a managed runner that already works that way.
jobs:
test:
runs-on: latchkey-small # one job per machine, destroyed afterwardHosted, self-hosted and ARC: one line, three stories
On GitHub-hosted runners this is rare and not yours to fix. An isolated occurrence is an infrastructure event on GitHub's side: re-run it, and escalate only if it repeats.
On a self-hosted fleet it is almost always operational: a configuration management run restarted the service, the host was patched and rebooted, an agent upgraded itself, or someone stopped a manually started runner. All of it is in the machine's own logs, which the job log cannot show you.
On Actions Runner Controller the usual cause is the pod going away underneath the job. A node upgrade, an autoscaler scaling in, or a drain for maintenance evicts pods, and a runner pod holding a job is evicted like any other. The ARC tracker carries a standing request for a way to protect in-flight jobs from node drain and auto-upgrade, which is the honest summary: the cluster does not know your job is halfway through.
What this message is not
It is not an out-of-memory kill: that ends the process with SIGKILL and reports exit 137, covered in exit code 137 in GitHub Actions. Here the step has no exit code at all, because the agent reporting it never got one back.
It is not a cancellation you triggered, although the two look similar. A canceled run says so on the run page and follows a documented signal sequence, covered in exit code 143 in GitHub Actions. Here the run was not canceled; the runner stopped being there.
It is closely related to "the runner has lost communication with the server", the same event from the other side: the agent going away is what the server notices when it stops hearing from it. If you see both across a fleet, treat them as one problem.
Ephemeral machines remove two of the causes
Two of the four causes below exist because a runner outlives jobs: a long-lived service that can be restarted, and a shared host that can be drained while work is on it. A machine that runs one job has neither.
That is how Latchkey managed runners are built. The documentation describes them as ephemeral virtual machines where each runner executes exactly one job and is destroyed afterward, registering just-in-time with single-use credentials on a private network with no inbound access. There is no long-lived agent service to restart and no shared host to drain, so the two most common self-hosted sources of this message do not arise.
That is a statement about machine lifecycle, not a claim that the failure cannot happen. What changes is that the usual operational causes are not present, because the thing they act on does not exist.
How to prevent it
- Schedule host patching and node upgrades outside peak CI hours.
- Alert on this message across the fleet, not per repository, so batches are visible.
- Keep long jobs on capacity that will not be reclaimed.
- Prefer one-job runners so there is no long-lived service to restart.
Frequently asked questions
What does "The runner has received a shutdown signal" mean?
Why do ARC runners receive a shutdown signal mid-job?
Is this the same as "lost communication with the server"?
Does re-running the job help?
Related guides
References
- actions/runner: the message in JobRunner.cs
- actions/runner issue 3724: the shutdown signal in a real fleet
- actions-runner-controller issue 4618: protecting in-flight jobs from node drain
- GitHub Actions: Actions Runner Controller and ephemeral runners
- Latchkey documentation: ephemeral, one-job runners
- GitHub Actions documentation