CI build suddenly failing with no code changes? The 9 usual causes
It passed yesterday. Nobody touched the code. Today it's red. Almost always, something around your code changed. Here's where to look, roughly in order of how often it's the culprit.
1. A dependency released a new version
If your manifest allows ranges (^1.2.0, ~=, >=) and the lock file isn't used in CI, you get whatever was published last night.
- Use the lock file in CI:
npm ciinstead ofnpm install,pip install -r requirements.txtwith pinned versions,poetry install,bundle installwithGemfile.lockcommitted. - Compare the installed versions in yesterday's and today's logs. The one that changed is usually the cause.
- Pin it to the last working version, then upgrade on purpose later.
2. The runner image changed
Hosted runners like ubuntu-latest and macos-latest move to new OS versions and update preinstalled tools. A new default Python, Node, Java or Xcode can break builds.
- Check the "Set up job" step in the log for the image version and compare it with the last green run.
- Pin the image (
ubuntu-24.04instead ofubuntu-latest) and pin language versions withactions/setup-node,setup-pythonorsetup-java.
3. An action or plugin version moved
A reference like uses: some/action@v3 follows a moving tag. A new release under the same major version can still break you, and old majors get deprecated (for example, actions running on retired Node versions).
- Pin actions to a full commit SHA for stability, and update them deliberately.
- Read the warnings at the top of the run summary. Deprecations usually show up there weeks before they become errors.
4. A cache went stale or got corrupted
Build caches keyed too loosely can restore files that no longer match your lock file.
- Key caches on the lock file hash, such as
hashFiles('**/package-lock.json'). - Delete the cache (in GitHub: Actions → Caches) and re-run.
5. A secret or token expired
Deploy keys, personal access tokens, cloud credentials and package-registry tokens all expire. The error is often a vague 401, 403 or "repository not found".
- Check when the token was created and its expiry. Rotate it and update the CI secret.
- Secrets aren't passed to workflows triggered from forks, so PRs from contributors can fail while your own pushes pass.
6. An outside service is down or rate-limiting you
Package registries, Docker Hub, GitHub's API and download mirrors all have outages and rate limits. Docker Hub limits anonymous pulls, for example.
- Check the provider's status page.
- Log in to registries in CI, even for public images, to get higher limits.
7. A test depends on the date, time or order
Tests that use "today", hard-coded dates, time zones, or that rely on running in a certain order start failing on a particular day or at random.
- Look for dates near now in the failing test: month ends, year ends, daylight saving changes, leap days.
- Freeze time in tests and run them in random order locally to flush out hidden dependencies.
8. A flaky test or a resource limit
Timeouts, out-of-memory kills (exit code 137) and "no space left on device" show up as a project grows.
- Re-run once. If it passes, it's flaky. Find it and fix it, don't just retry forever.
- For memory errors, lower parallel workers or raise Node's heap with
NODE_OPTIONS=--max-old-space-size=4096.
9. Someone changed a CI setting, not the code
Branch protection rules, required checks, environment variables, runner groups and organization policies change without a commit.
- Check the repo and organization audit log for recent settings changes.
Still red?
If you've been through the list and it's still failing, or you just don't have the afternoon to spend on it, we fix failing builds for a flat $99. You send the repo, we send back a pull request that makes it pass and a short note on what broke. You pay only after you've checked it works. The work is done by an AI coding system (Anthropic's Claude), and you review the change like any other PR.