Workflow-driven skill that builds the runbook for safely removing a feature flag from the codebase + the flag platform. Walks through: pre-removal verification (flag fully rolled out, no usage variance in evaluations, dependent code paths identified), the code-removal steps (delete the if-branches, simplify, restore types), the platform-side removal (archive in LaunchDarkly / Unleash / Flagsmith / GrowthBook), the verification post-removal, and the rollback plan. Use when removing a flag that has finished its mission (rollout-complete, experiment-shipped, kill-switch retired).
77
97%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Stale feature flags are technical debt: they bloat the codebase,
expand the flag-state matrix per
feature-flag-test-matrix-reference,
and confuse readers. But removing a flag wrong - deleting code
that's actually still gated for some users - is a regression.
This skill produces the runbook for safely removing a flag.
Before any code touches:
| Check | Pass criterion |
|---|---|
| Rollout 100% for all segments | LD/Unleash/etc. dashboard confirms |
| No alternate behaviour gated on this flag | Per-flag-page in platform shows no targeting variation |
| Evaluation count stable | Not a recently-changed flag; no recent variation flips |
| Dependent flags identified | Grep for the flag + any flag whose targeting references this flag's value |
| Owner / sponsor identified | Person who can authorise removal |
| Rollback plan exists | If removal breaks, how to restore |
Failure of any check = abort and resolve first.
Grep:
grep -rn 'show-new-ui\|"show-new-ui"' --include='*.{ts,js,py,go,java}' .For each match, classify:
| Pattern | Action |
|---|---|
if (client.isOn('flag')) → branch A else branch B | Delete branch B; remove the if |
| Conditional import / require | Delete the conditional; keep the new module |
| Conditional CSS / styling | Delete the old class; keep the new |
| Switch on multi-variant flag | Keep only the shipped variant |
| Test fixtures with the flag | Update or delete |
| Configuration with default = the old value | Update default; document |
For boolean flag at 100% rollout = true:
// Before
if (client.isOn('show-new-ui')) {
return <NewHeader />;
} else {
return <OldHeader />;
}
// After
return <NewHeader />;Delete <OldHeader /> component if not used elsewhere. Use
TypeScript / linter to find unused imports.
For multi-variant flag with treatment-b shipped:
// Before
const variant = client.getFeatureValue('checkout-experiment', 'control');
if (variant === 'treatment-a') { ... }
else if (variant === 'treatment-b') { ... }
else { ... } // control
// After (only treatment-b retained)
// (inline treatment-b's logic here)Keep the shipped variant's logic, inlined. Delete the others.
Per flag-state-coverage-builder:
flag-coverage.yaml to remove entries.# Find tests referencing the flag
grep -rn 'show-new-ui' tests/| Platform | Removal action |
|---|---|
| LaunchDarkly | Archive the flag (UI: flag → ⋯ → Archive). Don't delete - archived flags retain analytics. Per launchdarkly.com/docs |
| Unleash | Archive in the UI; deletion is permanent |
| Flagsmith | Delete feature (UI); the environment.json on next regenerate will lack it |
| GrowthBook | Archive in the UI |
Archive instead of delete where possible - retains historical analytics in case of regression.
Standard deploy. The flag is now:
Production should see identical behaviour as before removal.
Within 24h of deploy:
| Check | Pass |
|---|---|
| Error rate unchanged | No 5xx spike |
| Performance unchanged | p95 latency unchanged |
| User-facing behaviour stable | No "broken UI" support tickets |
| Platform shows zero evaluations of the archived flag | Confirms no code still references it |
If any check fails → roll back (revert the removal commit).
# If removal regresses something
git revert <removal-commit>
git push
# Then in the platform: un-archive the flag and re-set its prior valueThe rollback plan must be in the PR description for the removal - not memorized.
Add to the project's flag-history.md:
## 2026-05-20 - Removed `show-new-ui`
- Rolled out 100% on 2026-04-15
- Stable for 5 weeks at 100%
- Removed in commit abc1234
- Archived in LaunchDarkly
- Owner sign-off: @engineering-lead
- Rollback plan: revert + un-archiveFor stale-flag audits surfacing multiple removals: batch is faster but riskier:
| Batch size | When |
|---|---|
| 1 flag per PR | Default - easy to revert, clear analytics |
| 3-5 flags per PR | Same area of codebase; same owner |
| 10+ flags per PR | Periodic audit; experienced reviewer required |
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Remove flag at <100% rollout | Some users still see the old path; you break them | Wait for 100% + soak |
| Delete in platform but leave code | "Removed" but still evaluated → SDK errors on missing flag | Code first, then platform |
| Leave code but archive in platform | Code still references; defaults kick in; behaviour change | Either both or neither |
| No rollback plan | Hard to recover if regression | Always plan revert + un-archive |
| Skip post-removal verification | "It compiled; ship it" | Watch error rate / latency / tickets for 24h |
| Bulk remove without owner sign-off | Unilateral removal of someone else's work | Per-flag owner sign-off |
| Re-use flag name later | Confuses analytics, archive-then-create-new | New flag, new name |
| Forget to update tests | Tests still gate on the removed flag; mock returns default → wrong outcome | Step 4 |
| Leave dead variants from multi-variant | Half-clean removal | Inline shipped variant's logic |
This skill produces:
flag-history.md.feature-flag-test-matrix-reference,
flag-state-coverage-builder.launchdarkly-testing,
unleash-testing,
flagsmith-testing,
growthbook-testing.