The GitHub Actions Tax

mooreds1 pts0 comments

The GitHub Actions Tax | Production Ready #3

The GitHub Actions Tax

Issue #3workflow sprawl<br>The GitHub Actions Tax<br>byErik OstermanCEO & Founder of Cloud Posse

I love GitHub Actions. I've said it on LinkedIn, on recent podcasts, in customer calls. It's the easiest CI in the world to start with. The marketplace is enormous. Triggers are obvious. The free tier is generous. If you're shipping software in 2026, there's a good chance the first thing your developers reach for is .github/workflows/.

That's exactly why I keep watching teams pay for it the wrong way.

Not in dollars. The bill I'm talking about doesn't show up in the GitHub invoice. It shows up in maintenance time, debugging hours, the cost of onboarding a new engineer onto an undocumented YAML graph, and the slow accumulation of vendor lock-in dressed up as portability. I've started calling it the GitHub Actions tax , and most teams don't realize they're paying it until they try to leave or scale.

What "the tax" actually means

Walk into a typical infrastructure repo and look at .github/workflows/. You'll find a workflow that does twelve things. It checks out the code, configures AWS credentials, installs Terraform, runs terraform fmt, awks the output, posts a PR comment, runs terraform plan, parses the plan output, generates a custom summary, conditionally applies based on a label, comments again, updates a status check, and uploads an artifact.

Each of those steps was added on a Tuesday afternoon by someone solving a real problem. None of them are wrong individually. The workflow grew organically.

Now the workflow is the platform. Which means:

The platform only runs in CI. Want to test that flow on your laptop? You can't. act gets you 70% of the way there for the simple cases and silently diverges everywhere else.

The platform is debugged through echo. Something broke at step 9 of 12, and the only telemetry you have is whatever the previous engineer remembered to print to stdout. There's no --debug you can flip locally because the platform doesn't run locally.

The "declarative" YAML is decorated bash. What started as a config file is now encoding your system architecture in a thousand lines that's declarative in name only. Half the steps shell out to inline bash -c blocks. The other half use third-party actions whose internals are bash. It looks declarative because it's indented; it's procedural code in a language without functions, types, or tests — and it happens to be the canonical description of how your platform works.

The platform's security surface is enormous. Every action you pin is supply chain. Most of them aren't pinned by SHA. Most of them have transitive dependencies you've never audited. The pull_request_target patterns in your repo are exactly as safe as the third-party action you trusted last quarter.

The platform is invisible to onboarding. New engineers can't read 800 lines of YAML and understand what your deployment does. The workflow encodes a hundred decisions but documents none of them.

This isn't a GitHub Actions problem. Switching to GitLab CI, Bitbucket Pipelines, or Azure DevOps doesn't fix it — you'll just rebuild the same wall of YAML somewhere else. The problem lives one layer down, in the tooling . When tooling is doing its job, your pipelines are boring — short, readable, almost identical regardless of which CI vendor is running them. Boring pipelines are the goal. Workflow sprawl is what happens when CI absorbs responsibilities that don't belong in CI.

The Unix philosophy still applies

Here's the heuristic I keep coming back to: workflows should orchestrate, not implement.

The Unix philosophy says: write programs that do one thing and do it well. Compose them with pipes. The compose layer is dumb on purpose — it doesn't know how to do anything, it knows how to wire things together. That's its job.

A workflow should be the compose layer. Each step should be a real program — testable on a laptop, understandable in isolation, debuggable with the same tooling you'd use for anything else. The workflow's only job is to say "run this, then run that, in this order, with these inputs."

The principle has a name worth saying out loud: local reproducibility . Anything CI does should run the same way on a laptop. If a step only works on a GitHub-hosted runner, that's not a feature — it's a bug in your pipeline. When CI and the laptop run the same programs, you debug locally, you onboard new engineers in minutes, and the conversation about "which CI vendor" stops mattering. Same commands, same outputs, same exit codes — wherever they run.

The moment you start writing 200-line bash blocks inside run: keys, you've inverted the relationship. The workflow is no longer the compose layer — it's the implementation, and bash is the runtime. You've turned your CI into a programming environment, but a particularly bad one: no debugger, no local testing, no proper modules, slow feedback loop, expensive iteration...

github actions workflow platform bash doesn

Related Articles