remove-app
Cleanly tear down a crouton poc/app — the inverse of /deploy. Removes the code, the Cloudflare Worker + D1 + KV (both env scopes), the app's stray branches, and closes its epic/sub-issues — with a --d
SKILL.md.claude/skills/remove-app/SKILL.md
GitHubRemove-app Skill — Teardown
One-command teardown of a crouton poc/app — the inverse of /deploy. Where
crouton init + /deploy scaffold an app and auto-provision its Cloudflare
Worker + D1 + KV, this removes all of it: code, Cloudflare resources, GitHub
branches, and the tracking epic/sub-issues.
Always
--dry-runfirst. Every destructive step has a dry-run that lists exactly what it would remove and touches nothing. Only run the real teardown after the dry-run output looks right.
Why a CI workflow exists (the whole reason for this skill)
The interactive agent cannot do the two destructive halves itself:
- Cloudflare deletes — no
CLOUDFLARE_API_TOKENin the chat environment. - Branch deletes — the chat git proxy
403s on deleting refs.
So those run in CI via .github/workflows/teardown-app.yml (mirrors
db-clone.yml): Cloudflare deletes use CLOUDFLARE_API_TOKEN; branch deletes +
issue closes use the Nuxt Harness App token (like comment-dispatch.yml).
What the agent can do directly: remove the code (rm pocs/<app>) and the
docs references — those land as a normal PR on a feature branch.
What a teardown removes
| # | Surface | Who does it | How |
|---|---|---|---|
| 1 | Code — pocs/<app> or apps/<app> (+ deploy.config.json, layers, schemas, the per-app CI caller deploy-<app>.yml) |
the agent (PR) | git rm -r (default --delete) or git mv to retired/ (--archive) — commit via /commit, open PR |
| 2 | Cloudflare — Worker + D1 + KV for both scopes (<app> and <app>-staging) |
CI (teardown-app.yml) |
scripts/teardown-app.mjs → wrangler delete / d1 delete / kv namespace delete |
| 3 | GitHub — delete the app's branches (epic/<NN>-<app>, feat/*-<app>, claude/*-<app>), close the epic + sub-issues/PRs with a note |
CI (teardown-app.yml) |
github-script via the Harness App token |
| 4 | Labels (optional) — drop app:<app> / poc:<app> from .github/labels.yml |
the agent (PR) | Edit |
| 5 | Docs/refs — sweep docs/ + writeups/ for references, flag/remove |
the agent (PR) | Grep then Edit |
Guardrails
pocs/is the safe default. A poc has no production counterpart, so the default--scope stagingremoves only its staging Worker/D1/KV.apps/(production) needs an explicit, typed confirm. Deleting a prod scope (--scope prodorboth) aborts unless you pass--confirm <prod-worker-name>(mirrorsdb-clone's prod guard). Never delete production data without it.- Back up first for prod. Before tearing down a launched app's prod D1, take a
backup with the
db-cloneskill (clone prod → a keep-safe env) orwrangler d1 export. - This skill removes infrastructure on purpose — it is exempt from the "staging-only deploy" standing rule, but prod teardown stays deliberate + guarded, exactly like prod deploys.
Workflow
Step 1 — Identify the app & confirm it's removable
pocs/<app>(safe) orapps/<app>(prod — extra confirm). Note its tracking epic number andapp:/poc:label.- Find its branches and resources before deleting anything:
git branch -a | grep -i <app> # branches (also try short aliases, e.g. "libcat") node scripts/teardown-app.mjs --app <app> --scope staging --dry-run # CF resources
Step 2 — Dry-run the Cloudflare plan
node scripts/teardown-app.mjs --app <app> --scope staging --dry-run
This needs no credentials — it prints the exact wrangler deletes it would
run (resolving names from the app's wrangler.jsonc, or from the crouton naming
convention <app>[-staging] / <app>[-staging]-db / <app>[-staging]-kv if the
code is already gone).
Step 3 — Remove the code (agent, via PR)
On a feature branch:
Default (--delete) — delete the code outright:
git rm -r pocs/<app> # or apps/<app>
git rm -f .github/workflows/deploy-<app>.yml # the per-app CI caller, if present
Archive (--archive) — move the code to retired/ instead of deleting it:
git mv pocs/<app> retired/pocs/<app> # or git mv apps/<app> retired/apps/<app>
git rm -f .github/workflows/deploy-<app>.yml # the per-app CI caller, if present
Then write a .retired.json age-stamp into the archived directory (this is
written automatically during the archive flow):
cat > retired/pocs/<app>/.retired.json << 'EOF'
{
"archivedAt": "<current ISO timestamp>",
"sourceEpic": <epic number>,
"sourceDir": "pocs/<app>"
}
EOF
Schema: archivedAt is the ISO-8601 date of the archive commit, sourceEpic is
the epic being closed, sourceDir is the original path (e.g. pocs/blog or
apps/myapp). The retirement digest band and GC trigger read this stamp.
--archivepreserves the code as browsable reference underretired/while still tearing down all live Cloudflare resources, branches, and issues (Steps 4+). Archive != keep-deployed — the Worker/D1/KV are deleted either way.
Then sweep docs/refs and optionally drop the label:
grep -rn "<app>" docs writeups --include=*.md --include=*.html
# remove the `app:<app>` / `poc:<app>` entry in .github/labels.yml if dropping it
Commit with /commit referencing (#618) / the app's epic, and open a PR
(Closes <epic#> in the body). Run pnpm typecheck first.
Step 4 — Run the destructive teardown in CI
From the GitHub app: Actions → "teardown-app" → Run workflow. Inputs:
| input | for a poc | for a prod app |
|---|---|---|
app |
<app> |
<app> |
scope |
staging |
both |
dry_run |
true first, then false |
true first, then false |
delete_cloudflare |
true |
true |
branch_patterns |
<app> (add aliases: <app>,short) |
<app> |
close_issues |
epic + sub-issue numbers | epic + sub-issue numbers |
confirm |
(leave blank) | the prod worker name (<app>) |
Run once with dry_run: true → read the run summary (it lists every branch,
issue, and CF resource it would touch). If correct, re-run with dry_run: false.
Step 5 — Verify it's clean
git branch -a | grep -i <app>→ empty.- A fresh
crouton init <app>//deployworks with no leftover CF resource or branch collisions (the acceptance test in #618).
Reference case — library-catalog (the first dogfood)
library-catalog was torn down by hand (PR #606 + epic #566 closed), but two
things were left because the interactive env couldn't do them — this skill's first
real run cleans them:
- 14 stray branches:
claude/fix-libcat-lockfile,claude/issue-44{1,2}-*,claude/issue-454-*,claude/issue-471-*,claude/issue-567-*,claude/issue-569-*,claude/library-catalog-deploy-lds0jn,epic/{428,440,453,566}-library-catalog*,feat/{431,455}-scaffold-library-catalog. - Cloudflare staging: Worker
library-catalog-staging, D1library-catalog-staging-db, KVlibrary-catalog-staging-kv.
Note the lockfile branch uses the alias libcat, so pass both patterns. The
dispatch:
Actions → teardown-app → Run workflow
app: library-catalog
scope: staging
dry_run: true # then re-run with false
delete_cloudflare:true
branch_patterns: library-catalog,libcat
close_issues: (none — #606/#566 already closed)
confirm: (blank — staging only)
Relationship to #613
#613 is the automatic post-merge cleanup of a merged epic's branches. This skill is the broader, on-demand "remove the whole app" — code + Cloudflare + branches + issues. They're complementary; #613 stays the automatic path.
Credentials (CI)
teardown-app.yml reuses the secrets the deploy/db-clone workflows already use —
nothing new to set up:
CLOUDFLARE_ACCOUNT_ID+CLOUDFLARE_API_TOKEN(Workers Scripts / D1 / KV Edit) — for thewranglerdeletes.HARNESS_APP_ID+HARNESS_APP_PRIVATE_KEY(App with Contents: write + Issues: write) — for branch deletes + issue closes attributed tonuxt-harness[bot].