At 15:22 UTC on Thursday, August 6, 2026, GitHub reported degraded performance on Actions. By the time the incident was declared mitigated nine hours later, the tally was brutal: at peak, 71% of workflow runs were failing with infrastructure errors — and teams that "did everything right" by running self-hosted runners were down too. If your deploy path starts with "GitHub tells us the code changed, GitHub builds it, GitHub hosts it," then for the length of that incident you had no deploy path at all.
This post maps exactly what froze and what kept working that day, kills the comforting myth that self-hosted runners are an outage plan, and lays out a five-piece git-push design that keeps shipping the next time the forge goes dark.
Nine hours, 71% of builds failing
The August 6 incident began as degraded Actions performance and escalated to a major outage of Actions plus degraded GitHub Pages with deployment lag. GitHub's status page logged 28 updates before the degradation was declared mitigated around 00:06 UTC on August 7 — roughly nine hours after it started.
The numbers from GitHub's own incident report are stark: at peak, 71% of workflow runs experienced infrastructure failures, and 75% of the remaining runs were delayed by more than five minutes. New runs failed to start, existing jobs stalled, and scheduled pipelines sat frozen. It was not an isolated blip, either. A July 9 incident had already knocked out Actions job starts for over nine hours, a second August outage followed on August 17, and GitHub's August availability report counted five incidents for the month. In its July report the company called the August 6 incident "unacceptable in both its impact and particularity of its duration" and admitted, "we have fallen short of our commitments to you."
The trigger, per GitHub's postmortem coverage, was almost insultingly ordinary: a routine deployment reduced pod capacity in one datacenter, saturating the service mesh and cascading across clusters. The Actions services were already running close to their capacity and concurrency limits, and a latent bug made it worse — runners were assigned jobs that were no longer valid, then got stuck retrying them, holding back real work while the queues drained at a crawl.
The blast-radius map: what froze, what kept working
Here is the core artifact of this post: every GitHub surface your pipeline might depend on, what it froze on August 6, and — just as important — what kept working anyway.
| GitHub dependency | What froze | Who was down because of it |
|---|---|---|
| Hosted runners | Builds, tests, scheduled jobs | Everyone building only on ubuntu-latest |
| Self-hosted runners | Job dispatch — runners sat idle, unable to pick up work | Teams that moved compute home but kept GitHub's dispatch |
| Actions REST API | Programmatic retriggers, status checks, merge queues | Anyone scripting deploys or gating merges on checks |
| Webhook deliveries | Push/PR events to external CI, PaaS auto-deploy, chatops | Pipelines triggered by GitHub events, including some PaaS "deploy on push" flows |
| Pages builds | Static site rebuilds and publishes | Docs, blogs, and marketing sites on Pages |
| Raw-content fetches | Install scripts and build steps pulling from raw.githubusercontent.com | Builds downloading dependencies straight from GitHub at build time |
| Copilot agent/review | Agent-assigned coding tasks and automated review | Teams with agents in the merge path |
And the survivor column — the things that kept working through all nine hours:
- Plain git. Push, pull, fetch, and clone against any reachable remote never depended on the Actions control plane. Code kept flowing everywhere except through GitHub's own automation.
- Workloads already running. Nothing about the outage touched your production containers, VMs, or edge deployments. The incident froze change, not serving.
- Artifacts already in your own registry. Images, tarballs, and caches sitting on infrastructure you control stayed usable all night.
- Hosts independent of Pages. Any static site or service deployed to your own VPS, object storage, or CDN kept serving and remained redeployable by any path that did not route through GitHub.
Read the two columns together and the design principle falls out: everything that survived was something the team could reach without asking GitHub's permission first. Resilience here is not about redundancy inside GitHub — it is about how much of your path to production works when GitHub is a 503.
Why self-hosted runners didn't save anyone
This is the uncomfortable row in the table, so let's be explicit. Self-hosted runners — your hardware, your network, your autoscaling — appeared on GitHub's own impacted-surfaces list alongside hosted runners. Teams running the Actions Runner Controller watched runners sit idle while jobs queued: the incident froze dispatch, and dispatch lives in GitHub's control plane regardless of where the compute lives.
The failure mode is architectural, not operational. A self-hosted runner long-polls GitHub asking "do you have work for me?" When the service that answers that question is saturated, your runner fleet is a row of parked cars with no dispatcher. Operator reports from that night tell the same story: runners idle, jobs queued, nothing starting — including work that had nothing wrong with it.
This does not mean self-hosted runners are pointless — they control cost, data residency, and hardware choice. It means they are a compute decision, not a resilience decision. Owning the compute without owning the dispatch bought exactly zero minutes of uptime on August 6. The design below owns both.
The outage-proof shape: five pieces
A git-push path that survives an August 2026-style outage has five pieces. None of them is exotic; the point is that together they remove GitHub from the critical path of every deploy step.
1. CI logic in portable scripts, not just workflow YAML. If your build only exists as steps in .github/workflows/, you cannot run it anywhere else on short notice. Extract the real work — install, lint, test, build, package — into scripts (say, scripts/ci/) that run on any machine with the toolchain, and let the workflow file be a thin caller. This is the unglamorous prerequisite every other piece depends on: portable logic is what makes "run the same build somewhere else" a one-line change instead of a rewrite at 2 AM.
2. A second push remote. Add a self-hosted forge — Gitea or its community fork Forgejo are the usual picks, a single binary with mirroring built in — as a second remote every developer pushes to, or configure it to continuously mirror your GitHub repos. Either way, when github.com degrades, git push still has somewhere to go, and every downstream step can read code from the mirror. Push to both remotes in normal times so the failover remote is never stale when you need it.
3. A build trigger independent of GitHub webhooks. Webhook deliveries were among the degraded surfaces, so any "GitHub tells us to build" trigger is load-bearing on the incident. The mirror-side fix is small: Forgejo Actions can run your portable scripts on push to the mirror, or a poller on your own scheduler can watch the mirror's refs and kick off Tekton, Jenkins, or a plain build script on your machines. The invariant is that a push reaching your infrastructure starts a build without any GitHub event firing.
4. Local dependency and artifact caching. Builds that download toolchains, packages, or install scripts from GitHub-hosted URLs at build time inherit the incident. Vendor what you can, run a pull-through registry or artifact proxy for containers and language packages, and keep a warm cache of the release artifacts on your own disk. The August outage was a vivid demo: the teams still deploying at 20:00 UTC were the ones whose builds never made an outbound call to GitHub mid-run.
5. A deploy destination independent of Pages. Actions was not the only degraded surface — Pages builds lagged too. If your release step is "GitHub publishes the site," you survived the build outage only to strand the release. Ship static output to object storage plus a CDN, or to your own VPS behind your own TLS, pushed from your own machines. The same logic covers preview environments and docs: anything GitHub serves for you needs a second home, or the deploy half of "deploy path" is still single-sourced.
A minimal working setup
Concretely, here is the smallest version of all five pieces for one repository, end to end — including the failover moment.
In normal times, developers push to both remotes:
git remote set-url --add --push origin git@github.com:acme/web.git
git remote set-url --add --push origin git@forgejo.internal:acme/web.git
git push origin mainThe Forgejo mirror runs the same portable scripts GitHub Actions calls:
# .forgejo/workflows/build.yml — thin caller, same scripts as GitHub
on: [push]
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- run: ./scripts/ci/build-and-test.sh
- run: ./scripts/ci/publish.sh # pushes to YOUR registry + static hostpublish.sh pushes the container image to your own registry and syncs static output to your own storage — never to Pages, never gated on a GitHub event. Dependencies resolve through your pull-through cache.
The failover moment, when the status page turns red: nothing in this setup needs GitHub to ship. Push to the mirror remote directly, the mirror-side workflow builds and publishes, and the release lands on your own destination. The operator's runbook is one line — "push to forgejo.internal; the rest of the path is unchanged" — because every step downstream of the push already runs on machines you own. Practice it once on a quiet afternoon (point your push at the mirror, watch a full build-and-publish complete with GitHub untouched) and it stops being a plan and starts being a path.
What this costs, and what to skip
The honest question is what this discipline costs, because "run your own everything" is how small teams burn out. Calibrated roughly:
- Solo dev / side project: a Forgejo instance is a single binary on the VPS you already rent; portable scripts are an afternoon's refactor; pull-through caching can start as "vendor the three big dependencies." Total: a weekend, then near-zero upkeep.
- Small team: add a second remote to the repo docs, one mirror-side workflow, and a registry (the forge ships one, or run a lightweight one beside it). Total: a few days of one engineer's time, mostly the script extraction.
- Platform team: mirror the whole org, run the build fleet on your own nodes with autoscaling, put artifact proxies and deploy destinations behind your standard observability. Total: a real project, but it is the same project as "we can build and ship during a cloud incident" generally.
If you can only do one piece, do piece 1 — portable scripts. It is the only piece with no infrastructure and it unlocks all four others. If you can do two, add the second remote: code that cannot move cannot ship. The caching and Pages-independent destination matter most to teams whose builds are network-hungry or whose public surface is a static site; if neither describes you, they can wait for the second pass.
Own the dispatch, not just the compute
August 6 was not a freak accident — it was the predictable shape of a control-plane outage at a forge operating near its limits, and GitHub's own incident cadence that summer (July 9, August 6, August 17) says it will not be the last. The lesson is narrower and more actionable than "distrust the cloud": audit your deploy path for every step that needs GitHub to say yes at deploy time — dispatch a job, deliver a webhook, serve a build input, publish the output — and give each one a second answer.
Self-hosted runners answered the compute question and left the dispatch question open. A second remote, portable scripts, an independent trigger, warm caches, and your own deploy destination close it. Build the path on a quiet week, drill the failover once, and the next nine-hour incident becomes somebody else's war room.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



