I used up GitHub Actions' 2,000 free minutes for the month in three days. A month's budget, gone in roughly a tenth of a month.
Lately I've been building side projects with AI coding agents like Codex and Claude Code. Features that used to take days now come together in a short time, so I've taken to building whatever idea crosses my mind just to see. These days, whenever I have a spare moment, on the train or in bed before sleep, I'm firing off instructions to an agent from my phone. Even while my own hands are idle, the pushes keep quietly piling up. This is where that got me.
It's a side project, so I want to keep costs down. Then again, I'd rather not trade away quality by skipping tests. So I moved some of the checks I was running on GitHub Actions to my own machine, using Husky to run them before every push. What follows is how that went, along with the homework I haven't done yet.
A queue at the checkpoint
CI is a bit like a checkpoint that code passes through before it joins the mainline. Every pass gets inspected, and every pass costs a toll, paid in run time. With AI multiplying the number of travelers, queues are starting to form at the gate.
And it isn't just side projects. In September 2026, Anthropic published "Agentic coding is straining CI. Here's how we scaled test impact analysis at Anthropic." At Anthropic, the amount of code each engineer ships per quarter is on average 8 times what it was in 2021–2025, and the number of CI jobs grew 25-fold in six months. The moment writing code stopped being the bottleneck, the load piled up on whatever inspects it.
That same month, Linear published "AI coding has made CI a bottleneck, so we reworked ours to keep up." Same story: AI sped up development, CI run time and cost became the problem, and they went to work on parallelizing tests and improving their execution infrastructure.
In Japan, the CTO of DeploAI wrote about why local CI matters in the AI era and how to run it. As AI agents took over more of the work, CI started to cap development speed, and while running self-hosted runners they hit one bottleneck after another: concurrency, CPU, disk I/O.
Reading these, I get the sense that the choke point in software development is moving. The cost of generating code has gone down. The cost of confirming that the generated code is correct has not. And this isn't a worry reserved for big engineering organizations. It happens on the desk of someone like me, too.
2,000 minutes, three days
GitHub Actions gives you a monthly allotment of free run time depending on your plan. On GitHub Free, that's 2,000 minutes a month for private repositories. Standard GitHub-hosted runners on public repositories are basically free, so everything here is mostly about private repositories.
In my case, for side projects built with Next.js and TypeScript, I run static analysis with Oxlint, format checks with oxfmt, type checking with TypeScript, unit tests with Vitest, E2E tests with Playwright, and an application build. The Playwright E2E tests are the heavy ones; depending on the workflow, a single run can go past 20 minutes.
I never paid much attention to it. But once I started developing with AI, the loop from fix to push got noticeably faster. CI finds an error, I fix it and push again, and GitHub Actions dutifully runs every single time. By the time I noticed, all 2,000 minutes were gone.
Three days.
Of course, AI isn't necessarily the only culprit. There's surely room to improve both the E2E run time and the workflow setup. Still, I learned firsthand that when the development loop speeds up, CI costs that never mattered before suddenly can't be ignored.
Checking your bags before you leave
My first idea for cutting run time was to verify things locally beforehand. Oxlint errors, formatting issues flagged by oxfmt, TypeScript type errors, failing unit tests. All of these can be caught on my own machine without bothering GitHub Actions.
Push code with those errors in it and you get turned back at the checkpoint, fix things, and get back in line. CI is obviously necessary to keep code quality up. But there's no reason to have the guard point out something you could have caught before leaving the house.
So I decided to use Git'spre-push hook. Git has a mechanism called Git Hooks that runs arbitrary scripts on operations like commit and push. To manage them, I used Husky.
Checking before push with Husky
Husky is a tool for managing Git Hooks with minimal fuss. In a Node.js project, you can install it with whatever package manager you normally use. Here I'll use pnpm.
First, install and initialize Husky.
pnpm add -D husky
pnpm exec husky init
This creates a .husky directory and adds a prepare script to package.json. A pre-commit hook shows up by default, but since I want to check before pushing, I'll use pre-push.
Next, bundle the pre-push checks into a single script in package.json. It looks like this.
{
"scripts": {
"lint": "oxlint",
"format:check": "oxfmt --check",
"typecheck": "tsc --noEmit",
"test:unit": "vitest run",
"check:push": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test:unit",
"prepare": "husky"
}
}
This runs lint, format checks, type checking, and unit tests in order. Because they're chained with &&, it stops the moment one of them fails. Adjust the script names and commands to fit your project.
Finally, create a file called .husky/pre-push with this one line.
pnpm check:push
That's it: every git push now runs the checks above. If a type error is left over, for example, pnpm typecheck fails and the push is aborted. Fix it and push again. This cuts down a good deal on GitHub Actions runs wasted on simple mistakes that could have been caught locally.
Note that Husky hooks can be skipped. In other words, checking your bags at home is no substitute for the checkpoint. The checks on GitHub Actions stay exactly where they are.
Not everything belongs at home
So should I just move every test from GitHub Actions intopre-push? Personally, I don't think so.
Playwright E2E tests, for instance, take a long time. Run them before every push and the waiting simply moves to my machine. Standing in your own doorway for 20 minutes to save on the toll defeats the purpose.
So a split roughly like this seems sensible.
| Check | pre-push | GitHub Actions |
|---|---|---|
| Oxlint | ○ | ○ |
| oxfmt | ○ | ○ |
| TypeScript type check | ○ | ○ |
| Unit tests | ○ | ○ |
| E2E tests | As needed | ○ |
| Build | As needed | ○ |
That said, if unit tests start taking more than a few minutes, I'd also want something that runs only the tests related to the change. The point isn't to move everything local. It's to sort out what needs to be checked on GitHub Actions from what can be handled at home.
While I'm at it, there's also the option of making the local checks themselves faster. On a different project, swapping ESLint and Prettier for Oxlint and Oxfmt made lint up to about 37x faster and formatting about 91x faster. I wrote that up in "Moving from ESLint and Prettier to Oxlint and Oxfmt Made Linting 37x and Formatting 91x Faster." If a check runs on every push, the shorter the wait, the better.
Don't send them to the checkpoint at all
There's one more thing I'd overlooked. When I'm firing off instructions from my phone, the agent tends to push dutifully after every fix and decide its next move based on the CI result. All I do is glance at the results on a small screen, so I figured that was fine. But that's essentially using the checkpoint as an exam hall, so of course the tolls pile up.
So when I give instructions from my phone, the default is now: no pushing. Once the local checks pass, the agent stops at a commit, and only when the work reaches a natural break does it push everything in one go. Five small fixes pushed separately go through the checkpoint five times; bundled together, they go through once.
Spelling this out every time gets tedious, so the quickest route is to write "don't push until told to" in a file the agent reads, such as AGENTS.md. Humans find it hard to hold off on pushing, but an agent holds off exactly as told. Frankly, this may be doing more good than Husky.
Does Husky lower the toll?
This needs some care. Adding Husky doesn't shorten GitHub Actions run time itself. Pass the local checks and push, and the checkpoint inspects you exactly as before. What Husky cuts is only the wasted CI runs caused by errors you could have stopped locally.
Here's a rough estimate. Say each GitHub Actions run takes 20 minutes and you push 100 times a month. Ten of those fail on errors you could have caught locally, and you fix and push again. If those 10 failures were prevented, and each failed run had been going the full 20 minutes, you'd save 200 minutes a month. That's 10% of the 2,000-minute free tier.
But that's a back-of-the-envelope number and nothing more. In practice, lint or type checking often fails first and CI finishes well under 20 minutes. And if multiple jobs run in parallel, the way billable minutes add up changes too. I plan to compare run history going forward to see how much this actually saved.
Rethinking the checkpoint itself
Given that I emptied the tier in three days, Husky alone is unlikely to be a real fix. If CI is running this often, the GitHub Actions setup needs a second look too. There are a few options.
First, cancel outdated workflows. When you push to the same branch several times in a row, CI for the older commits often doesn't need to finish. In GitHub Actions, settingconcurrency cancels older runs in the same group.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Just don't slap this onto workflows like deployments, where stopping midway causes trouble.
Next, run tests based on what changed. Fixing one line of a README shouldn't trigger E2E tests. Withpaths and similar filters, a workflow starts only when specific files change. That said, skipping a workflow you've set as a required check can affect whether a PR can merge, so configure it carefully.
Caching may help as well. If installing dependencies or building eats a lot of time, caching leaves room for improvement. If you're on pnpm in particular, it's worth setting up caching properly on the GitHub Actions side.
And the last resort is a self-hosted runner. Run GitHub Actions on a PC at home and CI no longer consumes the free minutes for GitHub-hosted runners. It's essentially building your own checkpoint, and it comes with upkeep: the cost of the machine, electricity, security, maintenance.
What's more, in an environment where AI agents kick off huge amounts of CI, moving to a self-hosted runner may just move the jam to CPU, memory, or disk I/O. That's exactly what DeploAI's article described. Relocating the checkpoint isn't enough; the inspection itself has to get more efficient.
When checking costs more than writing
Thanks to AI coding agents, even individuals can now build software at a pace that was unthinkable before. On the flip side, the faster we write, the more the CI that checks our work becomes a new source of worry. I'm living proof: 2,000 minutes in three days. I find it pretty interesting that infrastructure costs I never gave a thought to showed up the moment I started leaning on AI.
As a first step, I added Husky so that lint, format checks, type checking, and unit tests run locally before each push. Along with that, when I give instructions from my phone, I no longer let the agent push at will. Of course, that doesn't settle it. Next I plan to look at tuning trigger conditions on the GitHub Actions side, optimizing tests, and possibly setting up a self-hosted runner.
That's the story of how the checkpoint tolls got me. Now that writing code keeps getting cheaper, even in side projects we have to think as hard about how to check the code efficiently as about how to write it.
References
- Anthropic — Agentic coding is straining CI. Here's how we scaled test impact analysis at Anthropic
- Linear — AI coding has made CI a bottleneck, so we reworked ours to keep up
- The importance of local CI in the AI era and how to operate it — Zenn (Japanese)
- GitHub Actions billing — GitHub Docs
- Husky — Get started