bedda.tech logobedda.tech
← Back to blog

Vercel Auto-Deploy Failures: Silent Drops Fixed

Matthew J. Whitney
9 min read
devopscloud computinginfrastructureoutages

A Vercel auto-deploy failure that makes noise is manageable. A Vercel auto-deploy failure that makes no noise at all is the kind of thing that costs you a client.

That's what happened to us on Crowdia in late July. A push to main landed cleanly in GitHub, the Actions workflow went green, and absolutely nothing happened on Vercel's side. No failed build. No error email. No Slack notification from the Vercel integration. The deployment queue just... didn't move. Production was running code that was four commits behind what the team believed was live, and we didn't catch it for nearly two hours.

This post is a direct comparison of two operational stances: the passive assumption model (trust the webhook, trust the platform, wait for failures to announce themselves) versus the active verification model (poll for confirmation, treat every integration as an untrusted black box, and log the proof). I'll tell you exactly what broke, what we replaced it with, and why the active model wins without qualification.


The Passive Assumption Model: How Most Teams Run CI/CD

The default Vercel setup is genuinely good. You connect a GitHub repo, Vercel installs a webhook, and pushes to your configured branch trigger builds automatically. The Vercel Git integration documentation describes this flow clearly: a push event fires the webhook, Vercel queues the build, and the deployment URL updates when it completes.

For the vast majority of projects, this works perfectly and requires zero maintenance. That's the whole pitch, and it's a fair one.

The passive assumption model treats this as sufficient. The mental contract is: "I pushed, GitHub accepted it, Vercel will handle the rest." Monitoring, if it exists at all, watches for build failures rather than for missing builds. Alerts fire when something breaks visibly. The gap in coverage is the scenario where a build never starts at all.

That gap is exactly where we fell in.

Why Webhooks Drop

GitHub webhooks are HTTP POST requests to a Vercel-controlled endpoint. They are synchronous from GitHub's perspective: GitHub fires the request, gets a 200 back, and considers the job done. What happens after that 200 is entirely inside Vercel's infrastructure, and GitHub has no visibility into it.

GitHub's webhook documentation is honest about this: delivery is best-effort, and the platform provides a delivery log for debugging, but there's no built-in retry loop that guarantees the downstream system actually processes the event. If Vercel's intake endpoint accepts the request but drops it internally before it reaches the build queue, GitHub marks the delivery as successful and moves on.

This is the correct behavior from a protocol standpoint. It's also a silent failure mode that most teams don't anticipate until they hit it.

In our case, the Vercel dashboard showed no record of a build attempt for the affected commits. The GitHub webhook delivery log showed successful 200 responses. The gap between those two facts is where the incident lived.


The Active Verification Model: Treat Every Integration as Untrusted

The active verification model starts from a different assumption: the webhook fired and got a 200, but that proves nothing about whether the deployment actually happened. Confirmation requires an independent check.

This is the same principle that makes SQLite's reliability approach worth studying. Richard Hipp's team doesn't assume a write succeeded because the API returned success. They verify the data is actually there. That discipline, applied to CI/CD pipelines, is what separates teams that catch silent failures from teams that find out from users.

For Crowdia, we implemented this in three layers after the incident.

Layer 1: SHA-Polling After Every Push

We added a post-push verification step to our deployment workflow. After a push to main completes, a script queries the Vercel API to confirm that a deployment exists for that exact commit SHA. The Vercel REST API exposes a GET /v6/deployments endpoint that returns recent deployments with their associated commit metadata.

The script polls this endpoint on a 30-second interval for up to 10 minutes. If no deployment appears with the expected SHA, it fires an alert to our ops Slack channel and opens a GitHub issue tagged deploy-incident. If a deployment appears but stalls in a BUILDING state beyond our p95 build time (which for Crowdia sits around 4 minutes), it escalates separately.

This is real code running in production. I'm not going to paste a sanitized snippet here because the auth token handling and the specific Slack webhook format are environment-specific and would mislead more than they'd help. The shape of the logic matters more: poll by SHA, not by timestamp. Timestamps drift. SHAs are deterministic.

Layer 2: Manual Trigger Fallback with Audit Trail

The second layer is a manual deploy trigger that any engineer on the team can run when the SHA-poll script raises an alert. Vercel's API accepts a POST /v13/deployments request that can force a deployment from a specific Git ref. We wrapped this in a small internal CLI tool that logs the trigger event, the engineer who ran it, the timestamp, and the target SHA to a Postgres table we call deploy_audit.

This sounds like overhead. It paid for itself on the first use, three days after we built it, when we needed to prove to a Crowdia stakeholder exactly when a hotfix went live and who authorized it.

Layer 3: Deployment Verification as a Required Status Check

The third layer closes the loop at the GitHub side. We added a required status check that marks a branch as unprotected until the SHA-poll script reports a successful deployment. This means a PR can't be merged into a downstream branch until we have confirmed deployment proof for the current main state.

This is a cultural change as much as a technical one. It makes deployment verification a first-class part of the workflow rather than an afterthought.


Direct Comparison: Passive vs. Active on the Dimensions That Matter

Here's where the two models actually differ, without softening the gaps.

Failure Detection Speed

Passive: You find out when a user reports a bug that should have been fixed, or when you manually check the Vercel dashboard. In our incident, that was 110 minutes.

Active: The SHA-poll script would have caught the missing deployment within 30 seconds of the first polling interval completing. Alert-to-awareness time drops to under two minutes.

False Confidence Surface

Passive: Every green GitHub Actions run creates false confidence. The pipeline "passed" but you have no proof production updated. Teams in this model often don't realize how wide this gap is until an incident exposes it.

Active: False confidence is structurally harder to maintain. The SHA-poll either confirms the deployment or it doesn't. There's no ambiguous middle state.

Operational Complexity

Passive: Near zero. The Vercel GitHub integration is a one-time setup. Nothing to maintain.

Active: Real overhead. The polling script needs maintenance when Vercel API versions change. The audit table needs a retention policy. The CLI tool needs documentation. This is legitimate cost.

Incident Recovery Speed

Passive: Recovery requires diagnosing why the webhook dropped (often unclear), manually triggering a deploy, and verifying it landed. Without tooling, this is a stressful, manual process under pressure.

Active: Recovery is a single CLI command that logs itself. The diagnosis can happen in parallel or after the fact.

Audit and Compliance

Passive: Your deployment history lives in Vercel's dashboard. If you need to answer "what was live at 14:32 UTC on July 28," you're dependent on Vercel's log retention and UI.

Active: Your deploy_audit table is yours. Query it however you need. Export it to your compliance tooling. Retain it as long as your contracts require.


Summary

DimensionPassive AssumptionActive Verification
Detection speedHours (or never)Under 2 minutes
False confidenceHighLow
Operational costNear zeroModerate
Recovery speedSlow, manualFast, scripted
Audit trailVendor-dependentSelf-owned
Setup timeMinutesDays

The Verdict

Use the passive model if you are running a personal project, a prototype, or anything where a two-hour deployment gap has zero consequence. Vercel's default integration is genuinely reliable for the 99% case, and adding active verification to a hobby project is waste.

Use the active verification model if you are running production software with real users, SLAs, or revenue attached. The Crowdia incident would have been a minor ops note if we'd had the SHA-polling script in place. Instead it was a two-hour outage that required a client call to explain.

The broader lesson is not specific to Vercel. Cloudflare's recently discussed Meerkat synchronization tooling is solving a related class of problem: distributed systems that need to stay in sync across infrastructure boundaries require active coordination mechanisms, not passive assumptions about delivery. The same logic applies to your CI/CD pipeline.

Every integration you treat as a trusted black box is a silent failure waiting for the wrong moment to surface. GitHub fires the webhook and considers its job done. Vercel accepts the request and considers its job done. Nobody in that chain is responsible for confirming that your production environment actually updated. That responsibility is yours, and the only way to meet it is to verify actively.

We built the tooling. It runs on every push to main across Crowdia and KRAIN now. The two hours we lost in July was the last time we found out about a deployment gap from a user.

Have Questions or Need Help?

Our team is ready to assist you with your project needs.

Contact Us