关于 flag-cleanup
供 Claude Code 使用的技能,用于归档或永久删除 GrowthBook 过时功能标志。它会检查依赖和行为变化、查找代码引用,并在删除前引导清理代码。
主要功能
- 01归档标志或永久删除
- 02检查规则、实验和草稿版本
- 03比较归档前后的行为
- 04通过 Code References 或 Grep 查找引用
- 05删除前引导修改代码
适用场景
- 01确认标志不再需要后将其退役
- 02在代码库中查找并替换标志引用
- 03清理前检查生产环境行为变化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ growthbook/ skills --skill flag-cleanup安装后在 Claude Code 中输入/flag-cleanup确认已加载。
详细介绍
flag-cleanup
归档或删除过时的功能标志。有两种方式:归档(可撤销,软禁用)或删除(永久操作;始终先经过归档这一安全关卡)。此技能会协调代码清理与 Agent 的通用 Read/Edit 工具;如果已配置 GrowthBook 的 Code References API,则会显示其中的调用位置,否则会通过 Grep 搜索用户当前目录。
所有 API 调用都通过随附的辅助脚本完成:${CLAUDE_PLUGIN_ROOT}/scripts/gb-call。它需要 GB_API_KEY——在 shell 中设置,或由 /growthbook:gb-setup 写入 ~/.config/growthbook/.env。如果未设置或无效,gb-call 的错误消息会提示你返回 /growthbook:gb-setup。
必需输入
开始前向用户收集以下信息。提示用户补充缺失的信息。
- Flag ID — kebab-case 键。如果用户提供的是描述,先转到
flag-search进行解析。 - 操作 —
archive(可撤销)或delete(永久操作;包括归档)。根据措辞推断: “archive”/“disable” → archive;“delete”/“remove”/“clean up”/“get rid of” → delete。执行变更前请确认。
工作流
使用此清单跟踪进度。请勿跳过步骤或调整顺序;每一步都是下一步的前置条件。
- [ ] 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. 获取功能标志并验证安全前提条件
gb-call GET /api/v2/features/<flag-id>
从响应中提取:archived(布尔值)、defaultValue、valueType、environmentSettings(哪些环境已启用)、rules(完整数组,包括任何 experiment-ref 规则)、holdout(仅供参考)、project。
如果返回 404,请停止:“没有 ID 为 <flag-id> 的功能标志。”建议使用 flag-search 列出功能标志。
运行安全检查。如果触发以下任一项,请停止:
-
archived === true,且用户要求归档 → 已归档。请重新确认意图(“你说要归档;此功能标志已经归档——你是想删除它吗?”),或直接正常退出。 -
archived === true,且用户要求删除 → 这是之前清理会话的后续操作(或用户已在 UI 中归档)。询问:此功能标志已归档——看来你是在继续之前未完成的清理。你已经在代码库的调用位置内联了该功能标志的值吗?
- 是 → 我会跳过行为变更检查和代码清理流程;我们直接进入删除前的验证或回滚关卡。
- 否 → 我仍会先带你完成第 2–4 步,因为代码中可能还存在对此功能标志的引用。
如果回答是,跳到第 6 步(由于
archived: true → true不会更改服务端状态,因此第 5 步的归档操作不执行;当feature.archived与请求的值相同时,处理程序会提前返回)。 -
此功能标志设置了
neverStale: true。通过过时状态端点确认:gb-call GET '/api/v2/stale-features?ids=<flag-id>'如果响应包含
staleReason: "never-stale",请停止:此功能标志已标记为
neverStale——有人明确表示不应自动清理它(例如紧急关闭开关、运维切换项、许可证限制等)。如需继续,请先在 GrowthBook UI 的<host>/features/<flag-id>中移除neverStale标记,然后重新运行我。 -
存在指向
running状态实验的有效 experiment-ref 规则。一次批量调用即可覆盖常见情况:gb-call GET '/api/v1/experiments?trackingKey=<flag-id>&status=running'这是可行的,因为按照惯例,
experiment-launch会将trackingKey === flag-id。如果响应数组非空,请停止:此功能标志关联到实验
<exp-id>(<exp-name>),该实验仍在运行。请先停止该实验(使用experiment-stop),然后重新运行我。防御性复查。 如果批量调用返回空数组,但功能标志的
rules数组中存在experiment-ref规则,则该实验可能是手动关联的,且experimentId ≠ trackingKey。逐条遍历 experiment-ref 规则,获取每个实验作为备用检查:gb-call GET /api/v1/experiments/<experiment-id-from-rule>如果任何一个返回
status: "running",请使用相同消息停止。 临时发布检查。 获取关联实验时,也要留意任何满足status: "stopped"且enableTemporaryRollout: true的实验——这不会阻止清理(实验已停止),但步骤 2 必须使用胜出值作为内联替换值。提醒用户:“实验
<exp-id>正在进行临时发布——当前所有用户看到的都是胜出值。步骤 2 将使用胜出值作为内联替换值,而不是defaultValue。” -
引用了该标志的草稿实验。 优先级低于运行中的实验,但值得警告并确认。使用相同的批量查询,但更改状态筛选条件:
gb-call GET '/api/v1/experiments?trackingKey=<flag-id>&status=draft'如果结果非空,则发出警告:
注意:此标志是草稿实验
<exp-id>的trackingKey。删除该标志不会立即造成问题,但会导致该实验无法成功启动。是否继续? -
该标志存在未解决的草稿修订。 归档会丢弃草稿:
gb-call GET /api/v2/features/<flag-id>/revisions/latest如果返回 404,则不存在草稿——继续。如果返回了草稿,则停止:
此标志上有一个处于活动状态的草稿修订(
<version>)。清理此标志会丢弃这些待处理的更改。请先在 GrowthBook UI 的<host>/features/<flag-id>上发布或丢弃草稿。 -
/stale-features未将其标记为陈旧,且用户要求删除。 发出警告,但不要停止:提醒:GrowthBook 未将此标志标记为陈旧。它仍在
<env>中启用,且具有活动规则。是否继续?
2. 计算内联替换值并检测行为变化
归档后,所有规则都将停止生效——该标志会向所有调用方返回 defaultValue。内联替换值通常是 defaultValue,但并非总是如此——请参阅下方的临时发布情况。
先检查是否存在活动的临时发布。 对于标志中的每条 experiment-ref 规则,获取关联实验:
gb-call GET /api/v1/experiments/<experiment-id>
如果 experiment.status === "stopped" 且 experiment.enableTemporaryRollout === true:
-
实验的
releasedVariationId会告诉你哪个变体正在为 100% 的流量提供服务。 -
在
experiment-ref规则的variations数组中找到该变体的值。 -
所有用户当前看到的都是该值,而不是
defaultValue。 -
将其用作内联替换值。向用户明确发出警告:
“此标志正在进行临时发布。当前所有用户看到的都是胜出值
<winner_value>。清理后,所有用户都会切换到defaultValue: <default_value>。如果两者不同,正确的替换值是内联写入<winner_value>,而不是<default_value>。”
对于 valueType: "json",展示原始的 JSON 编码字符串,并让用户自行调整内联结构。
这一步要回答的核心问题是:清理是否会改变生产环境中的行为?
遍历 rules 数组,并标记之前与 defaultValue 不同的情况:
- 向目标细分群体提供不同值的
force规则。 - 活动的
rollout规则(coverage > 0)——即使没有条件也要标记。 experiment-ref规则:获取关联实验。如果实验已停止且正在临时发布,则胜出值就是差异所在(如上所述)。如果实验仍在正常分配流量(实验已停止,但没有临时发布),用户会分配到不同变体——归档会使所有人都切换到defaultValue,这会改变处理组用户的行为。
展示一个表格:
Behavior-change check for <flag-id>:
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?
如果表格为空(没有差异),则此次清理纯属形式上的操作,用户可以继续,无需进一步确认。如果任何规则存在差异,则停止并确认——这会导致实际的行为变化。
3. 查找调用位置
优先尝试 Code References:
gb-call GET /api/v1/code-refs/<flag-id>
响应格式为 {codeRefs: [{ repo, branch, platform, refs: [{filePath, startingLineNumber, lines, flagKey}] }, ...]}。每个(repo,branch)对应一份文档——如果 Code References 已从多个分支推送,同一个功能可能会有多条记录。
-
响应非空 → 按文件分组,向用户展示行级引用;如果涉及多个分支,则附上分支信息。注明数据来自 Code References(让用户知道这些数据可能与本地编辑相比已经过时)。
-
响应为空 → 可能是未配置 Code References,也可能是该标志确实没有被引用。在回退到 Grep 之前,确认工作目录:
Code References 未返回任何结果。我会在当前目录(
<pwd>)中搜索<flag-id>。使用此标志的代码是否位于此处?还是应该搜索其他位置?确认后,使用 Grep 工具在项目中搜索标志 ID(kebab-case 键,精确匹配)。仅搜索相关文件类型(源代码,不包括 vendored 目录)。
如果 Code References 和 Grep 都未返回结果,则提示:
找不到
<flag-id>的任何代码引用。如果你确定该标志没有在任何地方使用,请继续执行步骤 5。否则,请再次确认工作目录,或查看<host>/features/<flag-id>中的 Code References,检查是否有过时数据。
4. 引导用户内联替换
按文件批量处理,而不是按行处理。 一个文件中可能有多处引用同一标志;要求用户在同一个文件中分别批准 N 次 Edit 会带来不便。对于每个包含引用的文件:
- 使用 Read 工具加载完整文件(一次即可)。
- 展示每个文件的摘要:文件路径、引用数量以及引用该标志的行。
- 提议使用一次 Edit,将文件中的所有引用替换为
defaultValue(或用户确认的内联值)。 - Edit 将通过 Claude Code 的常规权限流程执行——用户审核并批准整份文件的更改即可。
- 用户也可以说“跳过此文件”(稍后处理)或“只编辑一部分”(仅对此文件改为逐条引用执行 Edit)。
跟踪已处理和已跳过的文件。用户表示“内联处理完成”(或没有调用位置)后,继续执行。
如果用户希望完全推迟代码清理(“现在只归档标志,我之后再清理代码”),允许这样做——归档可以撤销,用户可以稍后重新运行此技能,在删除之前完成代码清理。
5. 归档标志
echo '{"archived": true}' \
| gb-call POST /api/v2/features/<flag-id> -
服务端会发生什么。 这不是元数据补丁。设置 archived 会在服务端触发 createAndPublishRevision——创建并原子发布一个新版本。适用于任何 v2 发布的故障模式同样适用:
- 2xx → 已归档。继续执行步骤 6(删除路径)或步骤 8(仅归档路径)。
- 403,响应正文包含 "approval required" → 执行步骤 5a。
- 409 → 执行步骤 5b(合并冲突;在我们发出 GET 和归档 POST 之间,另一位参与者更改了该标志)。
- 其他 4xx → 停止并显示响应正文。
归档事件会出现在版本历史记录中——在步骤 8 中展示此信息。
5a. 归档需要审批
与 flag-targeting 相同的三选一分支,但归档和删除之间有一个关键的不对称之处:
你的组织要求在归档此标记前先获得审批。要继续:
A. 标准审核流程(推荐)——我会为此更改申请审核;团队成员会在 GrowthBook UI 的
<host>/features/<flag-id>页面批准;你重新运行我即可继续。B. 组织范围的绕过设置——管理员在 Settings → General → Approvals 中启用“REST API 始终绕过审批要求”。此设置同时授权归档和最后的删除步骤。
C. 按令牌绕过——使用具有
bypassApprovalChecks权限的 PAT。**此权限授权归档,但不授权删除。**按令牌设置的权限仅用于绕过审核工作流,不会覆盖破坏性操作限制。如果你的最终目标仅是归档,这种方式可行。如果你想删除,仍需要选项 B 中的组织范围设置(或由管理员在 UI 中删除)。在用户选择 C 之前,明确告知这一不对称之处——不要让他们到第 7 步才发现。
对于路径 A,为草稿申请审核并停止:
echo '{"comment":"Auto-requested by flag-cleanup"}' \
| gb-call POST /api/v2/features/<flag-id>/revisions/<version>/request-review -
不要尝试 submit-review——API 会拒绝自我审批。
5b. 归档时发生合并冲突
归档 POST 返回 409 表示草稿的基础版本已过期——在我们第 1 步(GET)和第 5 步(POST)之间,团队成员(或另一次 Agent 调用)发布了对此标记的更改。**不要自动变基。**停止并提示:
无法应用对
<flag-id>的归档——此标记自我上次查看后已发生更改。请按以下步骤解决:
- 在 GrowthBook UI 中打开
<host>/features/<flag-id>。- 协调此更改(将待处理的归档变基到最新版本之上,或放弃此次更改并重新运行我,以基于新的实时状态重新开始)。
冲突解决后,重新运行我。
原样展示冲突响应正文。POST /api/v2/features/<id>/revisions/<version>/rebase 端点可用于选择性解决冲突;v1 会出于与 flag-targeting 相同的原因保持谨慎——解决合并冲突需要人工判断每个冲突字段。
6. 验证或回滚关卡(仅适用于删除路径)
归档成功后,停止并向用户确认。在提示词中明确说明删除不可逆,而不只是放在用户可能没看过的防护措施中:
此标记已归档。目前此操作完全可逆——在 UI 中取消归档(或回复“rollback”),一切都会恢复原状。一旦删除,此标记及其所有修订版本都将永久消失,无法撤销。
删除前:
- 检查代码库中是否有任何内容因此失效。运行测试;部署到暂存环境;验证应用是否能在使用
defaultValue替代旧规则时正确运行;或者执行你们通常在移除标记后进行的检查。- 确认无误后,回复“proceed”,我就会删除。
- 回复“rollback”,我就会取消归档。
这是此技能的关键关卡。永久删除是一条单向路,用户应至少经历一个验证周期,暂时保持标记已归档但未删除的状态。即使用户催促,也不要将这两个步骤合并。
**如果用户选择“rollback”:**通过同一端点设置 archived: false,撤销归档:
echo '{"archived": false}' \
| gb-call POST /api/v2/features/<flag-id> -
此操作与归档一样,会经过相同的修订和发布流程——同样可能遇到需要审批和合并冲突的故障模式。确认操作顺利完成,告知用户结果,然后结束。
7. 删除标记(仅删除路径)
gb-call DELETE /api/v2/features/<flag-id>
- 2xx,且包含
{deletedId}→ 继续汇报。 - 403 → 几乎可以确定是
restApiBypassesReviews设置已关闭。该标记已归档,只是无法通过 API 删除。请向用户说明:贵组织要求先启用“REST API 始终绕过审批要求”设置,才能通过 API 删除标记。该标记已归档;你可以:
- 请管理员启用该设置(设置 → 常规 → 审批),然后重新运行我以完成删除。
- 在 GrowthBook UI 中手动删除,地址为
<host>/features/<flag-id>(归档的标记仍会列在那里)。
按令牌设置的
bypassApprovalChecks并不能授权此操作——它有意只用于绕过审查工作流,而非覆盖破坏性操作限制。 - 其他 4xx → 停止并显示响应正文。
服务端清理。 删除会移除功能记录、所有修订、所有修订日志,并解除所有在 linkedFeatures 中关联了此标记的实验。实验本身不会被删除——它们会继续存在,但跟踪键已失效。请在第 8 步说明这一点,让用户知道这些实验仍然存在。
8. 汇报
打印摘要:
- 标记 ID 和最终状态(
archived或deleted)。 - 对于归档:已创建修订历史记录(用户的审计日志中会显示该记录)。
- 对于删除:已解除关联的实验(但未删除);它们的跟踪键现在指向一个不存在的标记。
- 找到的调用位置数量、已编辑的文件数量,以及跳过的调用位置。
- 使用的内联替换值(该标记的
defaultValue),以便用户了解具体情况。 - UI 链接:
<host>/features/<flag-id>——仅对已归档的标记有用(已删除的标记会返回 404)。将GB_API_URL中的api.替换为app.,据此推导<host>。 - 限制警告:“请注意:我无法检测其他标记是否将此标记用作前置条件。如果不确定,请在删除前检查 GrowthBook UI 中的‘使用者’面板;也可以先归档、再删除前检查,因为归档操作可撤销。”
- 保留组警告(如果存在
feature.holdout):“此标记关联了保留组<holdout-id>。该保留组的linkedExperiments可能仍引用指向此标记的实验;这些实验的跟踪键现在已失效。如果实验分析依赖保留组状态完整,请在 GrowthBook UI 中审查该保留组。” - 建议的后续操作:重新运行 Code References(CLI/Action),以便控制面板反映清理后代码库的状态。
Guardrails
- 对线上标志,规范流程是在删除前先归档。 除非组织范围内设置了
restApiBypassesReviews,否则后端会拒绝删除未归档的功能。即使启用了该设置,为了留出安全确认时间,也应优先采用「归档→验证→删除」。 - 归档会触发修订并发布。 通过
POST /v2/features/<id>设置archived: true,会在服务端创建并发布一个修订。适用于任何 v2 发布的故障模式同样适用,包括需要审批(5a)和合并冲突(5b)。 bypassApprovalChecks对归档和删除的作用并不对称。- 归档:按令牌设置的
bypassApprovalChecks有效(updateFeatureV2.ts会检查组织范围和按令牌设置)。 - 删除:按令牌设置的
bypassApprovalChecks无效(deleteFeature.ts中有明确注释:「绕过审核工作流,不是覆盖破坏性操作限制」)。 - 只有组织范围内的
restApiBypassesReviews才能授权这两种操作。向用户展示审批选项时,不要混淆二者。
- 归档:按令牌设置的
neverStale: true标志是有意设为永久的。 如果用户要求清理此类标志,请暂停操作;用户需要先在 UI 中明确移除此标志。- 正在运行的实验会阻止清理。 如果某个标志的
experiment-ref规则指向一个状态为running的实验,则不能清理该标志;停止实验是另一项决策,应交由experiment-stop处理。 - 未解决的草稿会阻止清理。 显示该草稿,并先询问用户是要丢弃还是发布。
- 永久删除不可逆。 归档和删除之间应暂停操作,让用户确认没有任何问题。即使用户催促,也不要合并这两个步骤。在面向用户的提示中说明操作不可逆,而不只是写在本指南中。
- 代码清理需要按文件审批,并应分批处理。 按文件归类调用点,每个文件提议一次 Edit。不要跨文件批量处理;不要绕过 Agent 正常的 Edit 工具权限流程。
- Code References 可能已过期。 数据反映的是上次 CLI/GitHub Action 运行时的情况,而非当前工作树。如果用户表示有些编辑尚未反映在 Code References 中,应以工作树为准(或重新运行 CLI)。
/code-refs为空不代表「代码中未使用」。 可能是尚未设置 Code References,也可能是用户的工作目录不是正确的代码库。在认定标志没有被引用之前,务必先确认工作目录。- 一次批量查询实验胜过按规则逐个查询 N 次。 首选使用
listExperiments?trackingKey=<flag-id>&status=running;仅当批量调用返回空结果但存在experiment-ref规则时,才按规则逐个获取(处理手动配置的边缘情况)。 - 归档后,所有规则都会停止计算——调用方将获得
defaultValue。 第 2 步的目的不是计算每个用户的行为,而是通过列出与defaultValue不同的规则,检测清理是否会改变生产环境中的行为。 - 反向依赖检测不在范围内。 API 未提供「哪些内容依赖此标志」的反向查询功能。应在报告中说明这一限制,不要默默漏掉这种情况。
- 删除时不会明确清理 Holdout 关联。 功能删除处理程序不会更新 Holdout 记录。如果删除的标志关联了 Holdout,其
linkedExperiments可能会残留过期引用。当存在feature.holdout时,应在报告中提醒用户。 - 删除会解除实验关联,但不会删除实验。 曾引用此标志的实验会继续存在,其
trackingKey值会过期。在删除流程的报告中提及这一点。 - 删除后不要推送更新后的 Code References。 CLI/Action 会在下次扫描代码库时自然执行此操作。提前清除引用可能会掩盖代码实际上尚未更新、仍处于损坏状态的情况。
使用的端点
GET /api/v2/features/<id>— 获取标志状态(规则、defaultValue、archived、environmentSettings、holdout、project)GET /api/v2/stale-features?ids=<id>— 确认是否过时 + 检查neverStale原因GET /api/v1/experiments?trackingKey=<flag-id>&status=running(以及&status=draft)— 批量查找关联到此标志的实验GET /api/v1/experiments/<id>— 针对手动关联这一边缘情况进行防御性交叉检查的回退方案GET /api/v2/features/<id>/revisions/latest— 检测尚未解决的草稿(404 表示没有)GET /api/v1/code-refs/<id>— 如果已配置 Code References,则获取行级调用位置;为空时返回{codeRefs: []}(而不是 404)POST /api/v2/features/<id>— 归档(请求体:{archived: true})或取消归档({archived: false});触发修订并发布DELETE /api/v2/features/<id>— 永久删除;对于线上标志,需要restApiBypassesReviews(未启用时会被拒绝)POST /api/v2/features/<id>/revisions/<version>/request-review— 仅在 5a 审批分支中使用
交接
flag-search— 在清理前查找并审计过时标志(flag-search中的 Path C 是自然的调用方)。flag-graph— 在归档或删除标志之前,检查依赖该标志的内容。flag-targeting— 如果用户实际上想改变标志的行为,而不是移除它。flag-revisions— 检查必须在归档前解决的未完成草稿。flag-publish— 处理归档时需要审批(5a)和合并冲突(5b)这两个分支。experiment-stop— 清理关联到运行中实验的标志前必须先执行。- 手动 UI 步骤(无技能):取消归档已归档但尚未删除的标志 — 在
<host>/features/<flag-id>完成。技能的回滚步骤适用于当前会话中的情况;在会话之外,用户需改用 UI。