# Flag Cleanup

> Archive or delete a stale GrowthBook feature flag, walking the user through inlining the flag's effective value at call sites in the codebase before removal. Use when the user says "delete this flag", "remove this stale flag", "clean up flag X", "archive this flag", "we don't need this flag anymore", or "get rid of this flag and its experiment-ref rule". For finding stale flags first, use flag-se…

- **Type:** Skill
- **Install:** `agentstack add skill-growthbook-skills-flag-cleanup`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [growthbook](https://agentstack.voostack.com/s/growthbook)
- **Installs:** 0
- **Category:** [Search](https://agentstack.voostack.com/c/search)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [growthbook](https://github.com/growthbook)
- **Source:** https://github.com/growthbook/skills/tree/main/skills/flag-cleanup

## Install

```sh
agentstack add skill-growthbook-skills-flag-cleanup
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# flag-cleanup

Archive or delete a stale feature flag. Two paths: **archive** (reversible, soft-disable) or **delete** (permanent; always goes through archive first as a safety gate). The skill coordinates code-cleanup with the agent's general Read/Edit tools, surfacing call sites from GrowthBook's Code References API (when configured) or falling back to Grep against the user's current directory.

All API calls go through the bundled helper: `${CLAUDE_PLUGIN_ROOT}/scripts/gb-call`. It needs `GB_API_KEY` — set in your shell, or written to `~/.config/growthbook/.env` by `/growthbook:setup`. If unset or invalid, gb-call's error message points back at `/growthbook:setup`.

## Required inputs

Collect from the user before starting. Prompt for what's missing.

- **Flag ID** — kebab-case key. If the user gives a description, route to `flag-search` first to resolve.
- **Action** — `archive` (reversible) or `delete` (permanent; includes archive). Inferred from wording: "archive" / "disable" → archive; "delete" / "remove" / "clean up" / "get rid of" → delete. Confirm before mutating.

## Workflow

Track progress with this checklist. Do not skip or reorder; each step gates the next.

```
- [ ] 1. Fetch flag, verify safety preconditions (incl. bulk experiment check)
- [ ] 2. Compute defaultValue + detect behavior-change divergences
- [ ] 3. Find call sites (Code References API → Grep fallback w/ cwd check)
- [ ] 4. Walk the user through inlining, batched by file
- [ ] 5. Archive the flag (revision-and-publish; branch to 5a on 403, 5b on 409)
- [ ] 6. Verify-or-rollback gate (delete path only)
- [ ] 7. Delete the flag (delete path only)
- [ ] 8. Report, including reverse-prerequisite limitation warning
```

### 1. Fetch flag and verify safety preconditions

```bash
gb-call GET /api/v2/features/
```

Capture from the response: `archived` (boolean), `defaultValue`, `valueType`, `environmentSettings` (which envs are enabled), `rules` (the full array, including any `experiment-ref` rules), `holdout` (informational), `project`.

If 404, halt: "no flag with id ``." Suggest `flag-search` to list flags.

**Run the safety checks. Halt if any of these fire:**

- **`archived === true` and the user asked to archive** → already archived. Either re-confirm intent ("you said archive; this flag is already archived — did you mean delete?") or exit clean.

- **`archived === true` and the user asked to delete** → this is a continuation of a prior cleanup session (or the user archived in the UI). Ask:
  > This flag is already archived — looks like you're picking up where a previous cleanup left off. Did you already inline the flag's value at call sites in your codebase?
  > - **Yes** → I'll skip the behavior-change check and code-cleanup walkthrough; we'll go straight to the verify-or-rollback gate before delete.
  > - **No** → I'll still walk through steps 2–4 first, since the code references may still exist.

  If yes, jump to step 6 (the archive step 5 is a no-op since `archived: true → true` makes no change server-side; the handler short-circuits when `feature.archived` equals the requested value).

- **`neverStale: true` is set on the feature**. Confirm via the staleness endpoint:
  ```bash
  gb-call GET '/api/v2/stale-features?ids='
  ```
  If the response includes `staleReason: "never-stale"`, halt:
  > This flag is marked `neverStale` — someone explicitly said it should never be cleaned up automatically (kill switch, ops toggle, license gate, etc.). To proceed, remove the `neverStale` flag in the GrowthBook UI at `/features/` first and re-run me.

- **Active experiment-ref rule pointing at a `running` experiment**. One bulk call covers the common case:
  ```bash
  gb-call GET '/api/v1/experiments?trackingKey=&status=running'
  ```
  This works because `experiment-launch` sets `trackingKey === flag-id` by convention. If the response array is non-empty, halt:
  > This flag is wired into experiment `` (``) which is still running. Stop that experiment first (`experiment-stop`), then re-run me.

  **Defensive cross-check.** If the bulk call returns empty but the flag has `experiment-ref` rules in its `rules` array, the experiment may have been manually wired with `experimentId ≠ trackingKey`. Iterate the experiment-ref rules and fetch each one as a fallback:
  ```bash
  gb-call GET /api/v1/experiments/
  ```
  If any returns `status: "running"`, halt with the same message.

  **Temporary rollout check.** While fetching linked experiments, also note any with `status: "stopped"` AND `enableTemporaryRollout: true` — these are not a blocker (the experiment is stopped), but step 2 must use the winner value as the inline replacement. Surface a reminder:
  > "Temporary rollout is active on experiment `` — all users currently see the winner value. Step 2 will use the winner value as the inline replacement, not `defaultValue`."

- **Draft experiment referencing the flag.** Lower-priority than running, but worth a warn-and-confirm. Reuse the bulk query with a different status filter:
  ```bash
  gb-call GET '/api/v1/experiments?trackingKey=&status=draft'
  ```
  If non-empty, warn:
  > Note: this flag is the `trackingKey` for draft experiment ``. Deleting the flag won't break anything immediately, but it will prevent that experiment from launching successfully. Proceed?

- **Unresolved draft revision on the flag.** A draft would be discarded by the archive:
  ```bash
  gb-call GET /api/v2/features//revisions/latest
  ```
  If 404, no draft exists — continue. If a draft is returned, halt:
  > There's an active draft revision (``) on this flag. Cleaning up the flag would discard those pending changes. Either publish or discard the draft in the GrowthBook UI at `/features/` first.

- **`/stale-features` doesn't flag this as stale and the user is asking to delete.** Warn but don't halt:
  > Heads-up: GrowthBook doesn't flag this as stale. It's still enabled in `` with active rules. Proceed?

### 2. Compute the inline-replacement value and detect behavior changes

After archival, **every rule stops evaluating** — the flag returns `defaultValue` for all callers. The inline replacement value is usually `defaultValue`, but not always — see the temporary rollout case below.

**Check for an active temporary rollout first.** For each `experiment-ref` rule in the flag, fetch the linked experiment:

```bash
gb-call GET /api/v1/experiments/
```

If `experiment.status === "stopped"` AND `experiment.enableTemporaryRollout === true`:
- The experiment's `releasedVariationId` tells you which variation is serving 100% of traffic.
- Find that variation's value in the experiment-ref rule's `variations` array.
- **That value — not `defaultValue` — is what all users currently see.**
- Use it as the inline replacement value. Warn the user clearly:

  > "Temporary rollout is active on this flag. All users currently see the winner value ``. After cleanup, all users will shift to `defaultValue: `. If these differ, inlining `` is the correct replacement — not ``."

For `valueType: "json"`, surface the raw JSON-encoded string and let the user adapt the inline shape.

**The real question this step exists to answer is: does cleanup change behavior in production?**

Walk the `rules` array and flag anything that previously diverged from `defaultValue`:

- A `force` rule serving a different value to a targeted segment.
- An active `rollout` rule (coverage > 0) — even one with no condition.
- An `experiment-ref` rule: fetch the linked experiment. If stopped with temporary rollout, the winner value is the divergence (handled above). If still assigning traffic normally (stopped experiment, no temporary rollout), users are split across variations — archiving shifts everyone to `defaultValue`, which is a change for treatment-group users.

Surface a table:

```
Behavior-change check for :
  defaultValue (post-cleanup):  "true"

  Pre-cleanup divergences (these stop applying after archive):
    production:  rule 1 (force, "beta testers" saved-group) served "false" → those users will now get "true"
    production:  rule 2 (rollout, 25% via id)              served "false" → 25% of traffic will shift to "true"
    staging:     no divergence — all traffic gets "true" today

Is this the intent?
```

If the table is empty (no divergences), the cleanup is purely cosmetic and the user can proceed without further confirmation. If any rule diverged, **halt and confirm** — this is real behavior change.

### 3. Find call sites

Try Code References first:

```bash
gb-call GET /api/v1/code-refs/
```

The response is `{codeRefs: [{ repo, branch, platform, refs: [{filePath, startingLineNumber, lines, flagKey}] }, ...]}`. One document per (repo, branch) — a feature may have multiple entries if Code References has been pushed from multiple branches.

- **Non-empty response** → present line-level references to the user, grouped by file, with branch info if multiple branches are represented. Cite that the data is from Code References (so the user knows it may be stale relative to local edits).
- **Empty response** → either Code References isn't configured, or the flag genuinely isn't referenced. Before falling back to Grep, **confirm the working directory**:
  > Code References returned no results. I'll search the current directory (``) for ``. Is that where the code that uses this flag lives, or should I look somewhere else?
  
  After confirmation, use the Grep tool to search for the flag ID (kebab-case key, exact match) in the project. Limit to relevant file types (source code, not vendored directories).

If both Code References and Grep return empty, surface:
> Couldn't find any code references to ``. If you're confident the flag isn't used anywhere, proceed to step 5. Otherwise, double-check the working directory or look at Code References at `/features/` for stale data.

### 4. Walk the user through inlining

**Batch by file, not by line.** A single file may have multiple references to the same flag; asking the user to approve N separate Edits in the same file is friction. For each file with references:

- Use the Read tool to load the full file (once).
- Surface a per-file summary: file path + count of references + the lines that reference the flag.
- Propose a single Edit that replaces *all* references in the file with `defaultValue` (or the user-confirmed inline value).
- The Edit goes through Claude Code's normal permission flow — the user reviews and approves the whole-file change once.
- The user can also say "skip this file" (handle later) or "edit some but not all" (fall back to per-reference Edits within just that file).

Track which files have been handled and which were skipped. When the user signals "done with inlining" (or there are no call sites), proceed.

If the user wants to defer code-cleanup entirely ("just archive the flag now, I'll clean up code later"), allow it — the archive is reversible and the user can re-run this skill later to finish the code-cleanup before deletion.

### 5. Archive the flag

```bash
echo '{"archived": true}' \
  | gb-call POST /api/v2/features/ -
```

**What happens server-side.** This isn't a metadata patch. Setting `archived` triggers `createAndPublishRevision` server-side — a new revision is created and published atomically. The same failure modes that affect any v2 publish apply:

- **2xx** → archived. Proceed to step 6 (delete path) or step 8 (archive-only path).
- **403 with "approval required" body** → step 5a.
- **409** → step 5b (merge conflict; another actor changed the flag between our GET and our archive POST).
- Other 4xx → halt with the body.

A revision-history entry will appear for the archive event — surface this in step 8.

### 5a. Approval required for archive

Same three-option branch as `flag-targeting`, **but with one critical asymmetry between archive and delete:**

> Your org requires approval before this flag can be archived. To proceed:
>
> **A. Standard review flow** (recommended) — I'll request review on the change; a teammate approves it in the GrowthBook UI at `/features/`; you re-run me to resume.
>
> **B. Org-wide bypass** — admin enables "REST API always bypasses approval requirements" in Settings → General → Approvals. This single setting authorizes *both* archive and the final delete step.
>
> **C. Per-token bypass** — use a PAT with `bypassApprovalChecks` permission. **This authorizes archive but NOT delete.** The per-token permission is intentionally a review-workflow bypass only, not a destructive-action override. If your end goal is archive-only, this works. If you want to delete, you'll still need the org-wide setting from option B (or an admin to do the delete in the UI). Surface this asymmetry to the user *before* they pick C — don't let them discover it at step 7.

For path A, request review on the draft and halt:

```bash
echo '{"comment":"Auto-requested by flag-cleanup"}' \
  | gb-call POST /api/v2/features//revisions//request-review -
```

Do **not** attempt `submit-review` — the API rejects self-approval.

### 5b. Merge conflict on archive

A 409 on the archive POST means the draft's base revision is stale — a teammate (or another agent invocation) published changes to the same flag between our step 1 (GET) and our step 5 (POST). **Do not auto-rebase.** Halt with:

> Your archive of `` couldn't be applied — the flag has changed since I last looked at it. To resolve:
>
> - Open `/features/` in the GrowthBook UI.
> - Reconcile the change (either rebase our pending archive on top, or discard and re-run me to start fresh against the new live state).
>
> Re-run me after the conflict is resolved.

Surface the conflict body verbatim. The `POST /api/v2/features//revisions//rebase` endpoint exists for opt-in resolution; v1 stays conservative for the same reasons `flag-targeting` does — merge resolution needs human judgment per conflicting field.

### 6. Verify-or-rollback gate (delete path only)

After archive succeeds, halt and confirm with the user. **Frame the irreversibility of delete in the prompt itself**, not just in a guardrail the user may not have read:

> The flag is archived. **At this point the action is fully reversible** — unarchive in the UI (or reply "rollback") and everything goes back. Once we delete, the flag and all its revisions are gone permanently; there's no undo.
>
> Before I delete:
>
> - Check that nothing in your codebase broke. Run your tests; deploy to staging; verify the app behaves correctly with `defaultValue` instead of the old rules; whatever your normal post-flag-removal check is.
> - When you're confident, reply "proceed" and I'll delete.
> - Reply "rollback" and I'll unarchive.

This is the load-bearing gate of the skill. Permanent deletion is a one-way door and the user should sit with the archived-but-not-deleted state for at least one verification cycle. Don't collapse the two steps even if the user pushes for it.

**If the user picks "rollback":** undo the archive by setting `archived: false` via the same endpoint:

```bash
echo '{"archived": false}' \
  | gb-call POST /api/v2/features/ -
```

This goes through the same revision-and-publish flow as the archive — same approval-required and merge-conflict failure modes apply. Verify it completes cleanly, surface the outcome, and exit.

### 7. Delete the flag (delete path only)

```bash
gb-call DELETE /api/v2/features/
```

- **2xx with `{deletedId}`** → proceed to report.
- **403** → almost certainly the `restApiBypassesReviews` setting is off. The flag is archived, just not deletable via the API. Surface:
  > Your org requires the "REST API always bypasses approval requirements" setting to be enabled before flags can be deleted via the API. The flag is archived; you can either:
  >
  > - Ask an admin to enable the setting (Settings → General → Approvals), then re-run me to finish the delete.
  > - Delete manually in the GrowthBook UI at `/features/` (the archived flag is still listed there).
  >
  > Per-token `

…

## Source & license

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

- **Author:** [growthbook](https://github.com/growthbook)
- **Source:** [growthbook/skills](https://github.com/growthbook/skills)
- **License:** MIT

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

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-growthbook-skills-flag-cleanup
- Seller: https://agentstack.voostack.com/s/growthbook
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
