The short version
  • On a static site, a fix you push is invisible to Google until a build succeeds and deploys, and a failed build leaves the previous version serving with no error anywhere.
  • On August 11, 2026, one package install on a Windows machine removed two entries from this site's lockfile (the file that pins every package version), and every push failed to build for 90 minutes while the live pages never changed.
  • The check that should have caught the broken lockfile, the standard dry-run install check (npm ci --dry-run), reported 'up to date' on it, so only the real Linux build exposed the fault.
  • A push that produces no deployment newer than your commit is a failed build once about five minutes pass, and the newest deployment is the last entry in wrangler's list, not the first.
Watch: Cloudflare build failed, site not updating for 90 minutes

A static site is a set of finished pages that a build step produces from source files. The live site changes only when a build succeeds and its result is deployed, meaning published to the servers that answer visitors and crawlers. When the build fails, nothing announces it. The previous build keeps serving, so the site looks healthy while every fix you push (send to the code repository) sits unpublished. On August 11, 2026, one package install on my Windows machine broke every build of this site for 90 minutes, and for search that means a fix Google cannot see until it deploys.1

On August 11, 2026, every push to brandonlazovic.dev failed to build for 90 minutes, and the site kept serving the previous build the whole time.

Why does a failed build leave the old site live?

On this site, a failed build leaves the old site live because a deploy replaces the live pages only after a build finishes. The build turns source files into finished pages, and the host swaps them in when that succeeds. If the build fails, nothing gets swapped, so visitors and crawlers keep receiving the previous version with no error page and no change in status code.

This site’s own incident record describes exactly that. In its words, “The site kept serving the previous build, so nothing looked broken from outside; the only visible symptom was that no new deployment appeared.”1 Cloudflare rebuilds this site on every push, so a push is a publish, and a push that never deploys is a fix that never went out.2

This site runs on Cloudflare as a Worker (a small program) that serves static files, and I have first-hand evidence only from that setup. The symptom, a failed build and an unchanged live site, is the one the search phrase “cloudflare pages build failed site not updating” describes, but I did not test a Pages project.

What happened during the 90 minutes of failed builds?

On August 11, 2026, one command, npm install -D playwright (npm installs a project’s add-on code packages, and Playwright is a browser-testing package), removed two entries from the file that pins every package version (the lockfile), and every push failed to build for 90 minutes. Because the lockfile fixes the exact version of every package, each build installs an identical set. With two entries missing, Cloudflare’s installer refused the file.1

The two missing entries were small helper packages that a few other packages need on some platforms. For anyone searching the error, they were @emnapi/core and @emnapi/runtime. Cloudflare’s build stopped with an error of this shape: npm error Missing: @emnapi/core@1.11.1 from lock file.1

The npm documentation for npm ci, the installer that builds run, sets a strict rule. The command “must have an existing package-lock.json”. It also says that “If dependencies in the package lock do not match those in package.json, npm ci will exit with an error, instead of updating the package lock.”4

The timeline below shows the window. The amber stretch is the span where every build failed and the live site did not change.

The 90-minute failed-build window on August 11, 2026A vertical timeline. First, an install on Windows drops two entries from the lockfile. Next, the first push fails on Cloudflare's Linux build. For 90 minutes every push fails and the live site keeps serving the previous build. Then the dropped entries are restored from the last good commit. Finally builds pass and a new deployment appears.One install on WindowsTwo entries drop out of the lockfileThe next push fails to buildCloudflare's Linux installer refuses the file90 minutesEvery push fails. The live sitekeeps serving the previous build.Entries restored from the last good commitCopied back verbatim, not regeneratedBuilds passA new deployment appears
The August 11, 2026 incident in order, not to scale. The amber stretch is the 90 minutes when no push could publish, from this site's own record.1

Why did the install work on my machine and fail on Cloudflare’s?

The install worked locally because my machine and Cloudflare’s build server run different software. Cloudflare builds with npm 10.9.2 on Linux, and this machine runs npm 11.6.2 on Windows. The newer npm on Windows cut out entries that do not apply to Windows and left in place the packages that depend on them. Linux then read a lockfile pointing at entries that were gone.1

The site’s record puts the mechanism this way: “The Windows resolver PRUNES platform-inapplicable transitive deps from package-lock.json while leaving the parents that reference them.”1 A transitive dependency is a package that another package needs, and pruning means cutting it out.

Nothing on the Windows side complained. The lockfile was wrong for Linux and looked fine everywhere I could see it, which is why the first sign of trouble was a deployment that never arrived.

Why didn’t npm ci –dry-run catch the broken lockfile?

The usual check, npm ci --dry-run, reported “up to date” on the broken lockfile, so it caught nothing. The command npm ci is what Cloudflare’s build runs to install exactly what the lockfile lists, and the dry-run flag simulates it without installing. With the folder of already-installed packages present, the simulation finished in about a second without comparing anything.1

I ran it specifically to rule this out, and it gave a clean answer on a file that fails the real command. Re-running npm install --package-lock-only, the command that rewrites only the lockfile, does not help either, because it prunes again.1

The repair was a restore. I took the dropped entries verbatim from the last lockfile that built, so no integrity hash was invented. An integrity hash is the fingerprint an installer uses to confirm it received the right file, and copying entries back means none of those fingerprints had to be made up.1

This site’s publish gate (the checks I run before publishing) now starts with a lockfile check that needs no built pages, so a fault like this one fails in milliseconds instead of after a build. The redirect headers piece lists it as the first of the gate’s scripts.

A stuck deploy costs you every fix and every page that depends on a build to appear. Google reads only what the live site serves, and the live site serves the last successful build. A corrected title, a redirect, a schema fix or a new article all stay unpublished until a build passes, and nothing in the site’s own pages signals the gap.

Scheduled articles show the cost most clearly. On this site, a future pubDate, the publish-date field in an article’s header, removes the page from the built site entirely, so a scheduled article is absent until a build runs on or after its date.3 An automated GitHub job named article-reveal.yml runs at 05:00 Coordinated Universal Time (UTC) and reveals any article whose date is today, and the article still needs a working build to become a page.3

My read, suspected and not observed, is that a reveal landing inside a failing window would publish nothing. No scheduled article fell inside the 90 minutes, so this is the mechanism followed to its end and not an incident.

The site’s own pre-publish check can’t see any of this either. That check, npm run audit, reads files on disk, and a pass on a local build says nothing about whether Cloudflare’s build passed on the same push.

How do you tell a failed deploy from a slow one?

Compare the time of your latest commit with the time of the newest deployment. If a deployment newer than the commit exists, the push published. If the newest deployment is older than the commit and about five minutes have passed, the build failed.

By the script’s own note a build takes a minute or two, and one recorded run on September 29, 2026 deployed 293 seconds after its commit, so treat five minutes as the minimum wait before calling a build failed.2

To run the check by hand on a Workers project, follow these steps.

  1. Read the commit time with git log -1 --format=%cI.
  2. List the deployments with npx wrangler deployments list --name <your-worker-name>, using Cloudflare’s command-line tool, wrangler.
  3. Find the newest Created: line that starts at the left margin. The list prints oldest first, so the newest is the last one, and the indented Created: lines under Version(s): belong to versions, not deployments.
  4. If that time is later than your commit, you published. If it is earlier and the five minutes have passed, open the Cloudflare build log.

Step 3 is where I got it wrong first. On September 18, 2026, a check I wrote by hand read the first entry, compared a days-old timestamp against the push time, and reported a failed build on a deploy that had succeeded 44 seconds after the push.2 A wrong alarm on every healthy deploy is worse than no check, because the false failures teach you to ignore the real one.

The durable fix is scripts/deploy-check.mjs, run with npm run deploy-check. It reports one of four states: the push published, the build failed, no deployment data came back because the list was empty, or the check is still pending because the five-minute window has not closed, which is neither a pass nor a failure.

The script’s tests include a copy of the buggy reader and confirm it gives the wrong answer on the same input, so they would have failed on my original mistake.2

Treat “pushed” as unfinished work on any static site until a deployment newer than your commit exists. Every SEO fix ends at the deploy, and a green local check cannot tell you whether it happened.

Sources

This site's own deploy incident record from August 11, 2026, and its deploy-check script and tests from September 18, 2026. Both are first-hand.

  1. First-hand: brandonlazovic.dev deploy incident of August 11, 2026, as recorded in this site's CLAUDE.md (Deploy trap section)
  2. First-hand: this site's scripts/deploy-check.mjs and scripts/deploy-check.test.mjs, written September 18, 2026
  3. First-hand: this site's CLAUDE.md notes on scheduled articles and the article-reveal workflow
  4. npm documentation: npm ci (lockfile requirement)