AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Vercel Preview Cleanup

skill-stealth-factory-skills-vercel-preview-cleanup · by stealth-factory

Install a GitHub Actions workflow that deletes a branch's Vercel PREVIEW deployments when that branch is deleted — every deployment it accumulated, not just the aliased URL (Vercel keeps one immutable deployment per push, so a 15-push branch leaves 15 live URLs). Requires Vercel; if you just want stale git branches deleted, use the branch-cleanup skill instead. Use when asked to clean up / delete…

No reviews yet
0 installs
11 views
0.0% view→install

Install

$ agentstack add skill-stealth-factory-skills-vercel-preview-cleanup

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-stealth-factory-skills-vercel-preview-cleanup)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
17d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Vercel Preview Cleanup? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Vercel preview cleanup

Authors a GitHub Actions workflow that removes a branch's preview deployments once the branch is gone. This skill installs workflows; it never deletes anything itself. At runtime there is no agent and no model call — deterministic bash + curl only.

Verified API detail (endpoints, pagination, rate limits, retention) lives in [reference/vercel-api.md](./reference/vercel-api.md) — read it before changing the delete logic.

Step 0 — prerequisites (check before installing)

This skill is downstream of branch deletion. If branches are never deleted, the delete event never fires and this workflow looks broken when it isn't.

  1. delete_branch_on_merge is on:

gh api repos/{owner}/{repo} --jq .delete_branch_on_merge → if false, install [branch-cleanup](../branch-cleanup/SKILL.md) first (it enables this and adds the closed-unmerged + orphan-sweep paths).

  1. A branch-cleanup workflow exists (.github/workflows/ — closed-unmerged path).

Without it, only merged branches get deleted; abandoned ones linger forever and so do their previews.

Install branch-cleanup first. It is the prerequisite, not an optional companion.

> Neon's Vercel integration reaps preview database branches when the git branch > disappears — no code needed here. It is another reason timely branch deletion matters.

The problem this solves

Vercel creates a new immutable deployment per push. The branch alias repoints to the newest; every older deployment stays live at its own URL. A branch with 15 pushes leaves 15 reachable deployments. Deleting the branch removes none of them. You must enumerate and delete all of them.

Why on: delete, not pull_request: closed

  • Deployments are keyed to the branch, not the PR. A branch pushed without a PR still

produces deployments; a PR-close trigger never sees them.

  • delete fires however the branch went away — merge auto-delete, manual, or `gh pr

merge --delete-branch`.

  • In stacked workflows PRs close and reopen during restacks. A PR-close trigger would

delete previews for stack entries still in active use.

delete event caveats (all handled in the templates)

| Caveat | Handling | | --- | --- | | Fires for tags too | every job gated on github.event.ref_type == 'branch' | | No branch filter supported (unlike push) | filtered in-job; expect noisy run history | | Workflow must exist on the default branch | documented in the caller; it runs the default-branch definition because the branch is already gone | | github.ref is useless here (resolves to default branch) | read github.event.ref | | github.event.ref is the bare name | do not strip a refs/heads/ prefix that isn't there | | Documented cap: not triggered when deleting >3 tags at once (branch equivalent unverified) | the reconciliation sweep is the backstop either way |

The four safety layers

Deleting the wrong thing here means destroying a production deployment, so the guards are layered and independent:

  1. The branch must be goneon: delete guarantees it, and because

workflow_dispatch does not, branch mode explicitly verifies the branch is absent before deleting anything. (Without that check a dispatch could wipe a live branch's previews, including the aliased one.)

  1. Include pattern */* (default) — only branches containing a / are cleaned, so

flat trunks (main, master, develop, staging, production) can never match.

  1. Protected denylistrelease/* and hotfix/* do contain a slash and would

pass layer 2, so they're denied explicitly. Layer 2 alone is not sufficient.

  1. target != "production" in code — the hard guard, applied to every listed

deployment. ⚠️ Preview deployments carry target: null, not "preview" — selecting .target == "preview" matches zero rows and silently deletes nothing.

Both guards are re-applied per-candidate in the sweep too, not just on the event path — and because deletes are paced at ~3 s each, the sweep re-verifies each ref is still absent immediately before deleting rather than trusting the live-ref snapshot it built at the start (a branch recreated mid-sweep would otherwise lose its previews). The step summary reports counts per run (found / deleted / kept / failed) — enough to notice a filter that's excluding everything, though it does not list every kept ref.

Runtime behaviour

  1. List every deployment for the branch — GET /v7/deployments, scoped by projectId,

teamId, branch.

  1. Paginate to exhaustion. pagination.next is a millisecond timestamp passed

back as until; stop only when it is null. Partial pagination silently orphans the oldest deployments — the exact problem being solved.

  1. Filter: drop target == "production" and already-deleted tombstones

(delete is a soft delete — they reappear in later lists).

  1. Cross-check each deployment's ref client-side before deleting — never trust the

server-side branch= filter alone.

  1. Delete each, paced at ~1 per 3 s. Vercel allows 200 deletes per 600 s per team;

a collapsing stack or a backfill will hit it. 200/404/410 all count as success.

Install

  1. Host [templates/reusable-vercel-preview-cleanup.yml](./templates/reusable-vercel-preview-cleanup.yml)

once in the org's .github repo. Do not copy it per repo.

  1. Add [templates/caller-vercel-preview-cleanup.yml](./templates/caller-vercel-preview-cleanup.yml)

to each repo's default branch (~30 lines). Replace YOUR-ORG and keep the uses: pinned to a full commit SHA (the template ships an obvious placeholder so it cannot silently run unpinned; a tag is mutable and can be moved). It forwards a delete-capable token; @main would let any change there take effect across every repo at once.

  1. Set per-repo vars.VERCEL_PROJECT_IDS (comma-separated — a monorepo maps one git repo

to several Vercel projects) and vars.VERCEL_TEAM_ID; set secrets.VERCEL_TOKEN.

  1. Private repos: the hosting repo's Actions access settings must permit other org

repos to call its reusable workflows, or every caller fails.

  1. Test via workflow_dispatch with dry_run: true — you cannot test on: delete

from a feature branch.

Installation is idempotent: if a workflow already exists, diff it and offer an upgrade — never clobber local edits without showing them first. Validate generated YAML (actionlint if available, otherwise a YAML parse) before writing.

Fork PRs — the event path does NOT cover them

Be clear about this before installing. A fork PR's branch lives in the fork, so:

  • on: delete never fires for it — there is no ref in your repo to delete.
  • It never appears in repos/{owner}/{repo}/branches.

The only trigger that could clean a fork PR's previews the moment it closes is pull_request_target, and this skill deliberately does not use it. That trigger runs with your secrets in the base repo's context; combined with a VERCEL_TOKEN that can delete deployments, it is a supply-chain surface — and one actions/checkout added by a later edit turns it into remote code execution. Not worth it for this feature. If your org accepts that trade-off, the wiring is a pull_request_target: [closed] job gated to head.repo.full_name != github.repository; adopt it knowingly, and never check out fork code in it.

What you actually get instead:

| Fork PR state | Behaviour | | --- | --- | | Open | previews are protected — the sweep's "still live" set is repo branches ∪ every open PR head ref, so an in-review fork PR's previews are never deleted (without this union they'd look like orphans and be swept mid-review) | | Closed / merged | the ref is neither a branch nor an open PR head → the sweep collects it on its next run |

So forks are cleaned — just on the sweep's cadence (daily by default) rather than instantly. Two honest caveats:

  1. Latency. A fork PR's previews stay live until the next sweep. Tighten the cron if

that matters.

  1. Flat-named fork branches are never swept. GitHub's web editor defaults to

patch-1, which has no / and so fails the */* include guard by design. Those previews persist until Vercel's retention reaps them. Widening the pattern to catch them would also expose your own flat trunks — not a trade worth making.

Rolling out across many repos

  • Pin the reusable workflow to a full commit SHA. It holds delete

permissions; @main means every repo silently picks up any change to it.

  • Replace YOUR-ORG in the caller's uses: with your organisation.
  • VERCEL_PROJECT_IDS misconfiguration is the top operational hazard. Point a repo

at a project belonging to a different repo and the sweep sees all of that project's refs as orphans. Verify the mapping per repo before enabling sweep_delete.

  • Watch for failing sweeps. A daily sweep that errors is a red run nobody reads —

route the workflow's failure notifications somewhere a human sees.

Deliberately not built

  • No cross-branch dependency check. Deployments are immutable and branch-scoped —

unlike branches, nothing else can depend on one. A later pass should not add a "does anything else use this deployment" guard; there is nothing to check.

  • No pull_request_target. It's the only trigger that could clean fork PR previews

on close, and it runs with secrets in the base repo's context. Declined deliberately — see "Fork PRs" above for what covers them instead.

  • Retention is not a substitute. It's an independent backstop with its own floor

(last 10 project deployments, last 20 READY non-production, the latest preview of an active branch, …) and deploymentsToKeep is production-only. See [reference/vercel-api.md](./reference/vercel-api.md).

Backfill

The workflow only affects branches deleted from now on. For deployments already accumulated, run [scripts/vercel-backfill.sh](./scripts/vercel-backfill.sh) once — dry-run by default, same four guards, same pacing.

See also

  • [branch-cleanup](../branch-cleanup/SKILL.md) — the prerequisite. Deletes the

branches whose deletion triggers this skill. Install it first; it also works standalone for repos with no Vercel.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.