How Orphaned Neon Preview Branches Quietly Inflated Our Invoice (and the Script We Used to Fix It)
TL;DR
Neon preview branches tied to deleted GitHub branches do not auto-clean themselves. Over time, they silently accumulate and increase your monthly invoice. We discovered this the hard way. This post documents the exact operational problem, the audit mindset, and the battle-tested cleanup script we used to regain cost control—without touching production data.
This post focuses on one-time cleanup and auditing. For preventive automation, see Part 2.
The Hidden Cost Trap in Preview Environments
Preview environments are a competitive advantage—until they turn into financial liabilities.
Platforms like Neon make it trivial to spin up database branches per feature or PR. Combined with Git workflows and CI/CD, this feels “free” and harmless.
Reality check:
When GitHub branches are deleted, Neon preview branches often remain alive unless explicitly removed.
Result:
Idle databases
Zero traffic
Still billable
This is not a bug. It’s an operational gap.
The Symptom We Noticed (Too Late)
Our early warning signal wasn’t an alert—it was an unexpected invoice spike.
On investigation:
Dozens of preview branches existed in Neon
Corresponding GitHub branches were long gone
Some branches were weeks old, others months
At that point, manual deletion was not an option. We needed repeatable, auditable cleanup.
Design Goals for the Fix
Before writing a single line of code, we aligned on constraints:
✅ Zero risk to protected branches (
main,staging,production)✅ Read-before-delete (dry run first)
✅ Source of truth = GitHub, not local Git
✅ API-safe (rate limiting, batching)
✅ Human-readable output for audits and reviews
This led to a standalone cleanup script.
The Cleanup Strategy (High Level)
Fetch all Neon branches via API
Fetch all active GitHub branches from origin
Normalize naming (
preview/*handling)Diff the two sets
Flag orphaned Neon branches
Delete them only after confirmation
The Script (Used in Production)
⚠️ Important
Nothing below is redacted or simplified. This is the exact script we used internally.
Run withDRY_RUN=truefirst.
What This Gave Us Immediately
📉 Instant reduction in billable branches
🔍 Clear visibility into infra drift
🧠 A repeatable audit mechanism
🛡️ Zero risk to production
Most importantly, it exposed a deeper truth:
Manual cleanup is damage control—not prevention.
What’s Next (Automation)
This script solves the existing mess.
The real fix is eliminating the problem at the source—by automatically deleting Neon branches the moment a GitHub branch is deleted.
That’s exactly what we implemented next using GitHub Actions, and we’ll cover it in the follow-up post.
➡️ Coming next:
“Zero-Touch Neon Cost Control: Auto-Deleting Preview DBs on GitHub Branch Deletion”
