Skip to content
Top 1% Upwork (8 years) 286+ client deployments 2,036+ projects shipped GoHighLevel Certified Partner Featured speaker: GHL Summit 2025 Client Login
← All issues
The Scale Brief · Issue #156

Every push built a deployment.
Not one of them shipped a single byte.

The dashboard was green. Deployments were listed, timestamped, one per commit. And the live site hadn't changed in days. This is the anatomy of the silent deploy — the failure mode that hides in plain sight because it looks exactly like success.

Here is the worst kind of broken: the kind that looks fine. A pipeline that throws an error is a pipeline you fix in an hour. A pipeline that reports success while doing nothing can run for a week before anyone notices the site is frozen. That was this week. Here's the whole thing, because the pattern generalizes to every stack that auto-deploys from a Git push.

The setup

Standard modern static-site plumbing. Push to main on GitHub. A hosting provider (Cloudflare Pages, in our case, but Netlify and Vercel work identically) watches the branch, clones the repo, builds it, and swaps the live site. Zero-click deploys. It had worked for months.

Then it quietly stopped. The tell wasn't an alert — it was a human noticing that a copy change from three days ago still wasn't live.

The signature

Pull the deployment history and the shape of the bug jumps out immediately:

# deployment log, most recent first
06:43 production trigger: github:push stage: clone_repo FAILURE commit: 052f0c7
06:43 production trigger: ad_hoc stage: deploy SUCCESS
06:36 production trigger: github:push stage: clone_repo FAILURE commit: 177b7c6
06:36 production trigger: ad_hoc stage: deploy SUCCESS
18:06 production trigger: github:push stage: clone_repo FAILURE commit: 2ff1728

Two things are true at once here, and holding both in your head is the whole diagnosis:

  • Every push is reaching the host. A github:push deployment gets created on every commit. The webhook is alive.
  • Every one of those dies at the first stageclone_repo — before a single line of the build runs.

And the reason nobody screamed: interleaved between the failures, someone had been quietly running manual uploads (trigger: ad_hoc) that did succeed. So the live site was updating — just by hand, every time, while the automation underneath it was dead. The workaround was masking the outage. That is the trap. A manual fallback that works is a smoke detector with the battery pulled out.

Why the error message lies

Open the failed clone and it says this:

Cloning repository...
remote: Repository not found.
fatal: repository 'https://github.com/<owner>/<repo>/' not found
Failed: error occurred while fetching repository

"Repository not found" is a lie — or rather, it's GitHub being deliberately vague. The repo exists. Commits are landing on it every few minutes. What actually happened: the repo is private, and the build runner's credential lost permission to read it. When you ask GitHub for a private repo you're not allowed to see, it does not return 403 Forbidden — that would confirm the repo exists. It returns 404 Not Found, identical to a repo that was never there. GitHub refuses to leak the existence of private repos to unauthorized callers. Good security. Terrible for debugging, because the message points you at the wrong problem.

So why does the push webhook still fire if the credential is dead? Because the two are different mechanisms. The webhook notification ("hey, there's a new commit") and the clone credential ("here's a token to actually read the code") are separate grants. Revoke access to the repo and the notification can keep arriving while the clone gets slammed shut. The pipeline looks connected and is functionally severed.

Is it the webhook, or the clone? Read the stage.

This is the fork that tells you where to look. Do not guess — the failing stage names the culprit.

★ No deployment appears at all

  • The webhook itself is dead — the host never hears about the push
  • Cause: integration uninstalled, branch filter wrong, or the whole Git connection removed
  • Look at: the repo's webhook delivery log and the host's Git connection status

★ Deployment appears, dies at clone_repo

  • The webhook is fine — the credential lost repo access
  • Cause: app access revoked, repo recreated with a new ID, or made private without re-auth
  • Look at: the host's GitHub App / OAuth grant and its selected-repositories list

Our failures were the right-hand column: deployments created, clone denied. That points at exactly one thing — the connected app's access to the repository, not the connection itself.

The fix

When a host clones a private repo it does it as a GitHub App installation (Cloudflare Pages, Netlify, and Vercel all work this way). If that installation's repository access gets edited — someone switches it to "only select repositories," or the repo gets recreated and inherits a new internal ID the old grant doesn't cover — the app silently loses read access. The clone starts 404-ing and nothing else changes.

  1. Go to your GitHub account's installed apps (github.com/settings/installations).
  2. Open the hosting provider's app (Cloudflare Pages / Netlify / Vercel) and hit Configure.
  3. Under Repository access, confirm the deploying repo is actually in the list. If it's on "only select repositories" and the repo is missing, add it — or switch to "all repositories."
  4. Save. That re-grants the installation token, and the next clone reads clean.

Then verify the trigger type, not just the color. A green deploy triggered by a manual upload proves nothing — you want to see a fresh github:push deployment reach success on its own. That is the only signal that the automation is actually back.

What the receipts say

★ The masked outage
Every push failed. Zero alerts fired.
The pipeline had been failing at clone on every single commit for days. Nothing paged, nothing emailed. The only reason it surfaced was a person noticing stale copy on the live site.
★ The workaround that hid it
Manual uploads kept the site fresh — and the bug invisible
Because a human was hand-deploying after each failure, the live site stayed current. The fallback working is exactly what let the root cause survive. Fallbacks should be loud, not silent.
★ Time from real diagnosis to fix
One setting, under a minute
Once the failing stage was read correctly — clone, not webhook — the cause was unambiguous and the fix was four clicks in the GitHub App's repository-access list. The hard part was never the fix. It was noticing.

The lesson worth keeping

Your deploy monitor should not alert on "latest deployment failed." It should alert on "no push-triggered deployment has succeeded in N hours." Those are different questions, and only the second one catches the silent deploy. A pipeline that fails loudly is healthy. A pipeline that fails quietly while a human props it up is the one that costs you a week — and, on a site that sells, real money in stale prices, dead links, and copy that shipped everywhere except where customers could see it.

Same root disease as the fire-and-forget tax: work that reports success while quietly dropping the payload. The cure is always the same — instrument the thing that's allowed to fail, and make sure the failure is louder than the workaround.

★ TL;DR · the silent deploy in one breath Deployments appear on every push but die at clone_repo with "repository not found." The repo is private and the host's GitHub App lost read access; the 404 is GitHub hiding a private repo, not a missing one. Re-grant the app's repository access, then confirm a real github:push deploy goes green. Alert on "no push-triggered success in N hours," never on color alone.
★ Want your pipeline audited before it fails silently?
30 minutes. We look at how your site actually ships, find the stage that fails quietly, and wire the one alert that would have caught it.
Apply for the audit
Make the failure louder than the workaround.

The Scale Audit finds the stage that fails quietly
and wires the alert that catches it first.

30 minutes. We trace how your site actually ships — push to live — find the step most likely to fail in silence, and set the one signal that would have paged you on day one instead of day seven.

Apply for a free audit All issues