GitHub 自动化 — 基于 AI 的大规模 Issue 管理
前置知识
- ›第 1 篇:架构概览
- ›GitHub Actions 基础知识(工作流、事件、权限)
- ›TypeScript / Shell 脚本基础
GitHub 自动化 — 基于 AI 的大规模 Issue 管理
claude-code 仓库不只是存放 Claude Code 插件的地方——它还使用 Claude Code 来管理自身。这种递归式 dogfooding 模式贯穿了 12 个 GitHub Actions 工作流、自定义 slash 命令、沙箱化 CLI 包装器以及 TypeScript 生命周期脚本。有人提交 Issue 时,Claude Code 会对其分类;出现重复 Issue 时,Claude Code 会自动识别;Issue 过期时,脚本会在提供宽限期和作者否决机制的前提下将其关闭。
本文将梳理一个 Issue 的完整生命周期,并深入分析让 AI 代理在生产环境中安全运行于 GitHub 仓库之上的三层安全模型。
Issue 生命周期流水线
在 claude-code 仓库中,一个 Issue 最多会经历七个阶段,每个阶段由不同的工作流或脚本负责处理:
stateDiagram-v2
[*] --> Opened: Issue created
Opened --> Triaged: claude-issue-triage.yml
Triaged --> DupeChecked: claude-dedupe-issues.yml
DupeChecked --> Active: No duplicates found
DupeChecked --> DupeCommented: Duplicate detected
DupeCommented --> AutoClosed: 3-day grace + no dispute
DupeCommented --> Active: Author disputes (👎 reaction)
Active --> Stale: sweep.ts (14d inactive)
Stale --> Closed: sweep.ts (14d after stale label)
Active --> NeedsInfo: Triage labels needs-info/needs-repro
NeedsInfo --> Closed: sweep.ts (7d no response)
NeedsInfo --> Active: Info provided → re-triage
Closed --> Locked: lock-closed-issues.yml
生命周期标签及其超时时间统一定义在 scripts/issue-lifecycle.ts 中,作为唯一数据源:
export const lifecycle = [
{ label: "invalid", days: 3, reason: "this doesn't appear to be about Claude Code" },
{ label: "needs-repro", days: 7, reason: "we still need reproduction steps" },
{ label: "needs-info", days: 7, reason: "we still need more information" },
{ label: "stale", days: 14, reason: "inactive for too long" },
{ label: "autoclose", days: 14, reason: "inactive for too long" },
] as const;
所有需要超时信息的脚本和工作流都从这个文件导入。常量 STALE_UPVOTE_THRESHOLD(10 个点赞反应)提供了一个安全阀——热门 Issue 即使长期不活跃也不会被标记为过期。
AI 驱动的分类与去重
Issue 被提交后,两个工作流会并行触发:
claude-issue-triage.yml(位于 /.github/workflows/claude-issue-triage.yml)通过 anthropics/claude-code-action@v1 执行 /triage-issue 命令。它使用 Claude Opus(claude-opus-4-6),超时时间为 5 分钟。并发组机制确保同一个 Issue 同一时刻只有一个分类任务在运行。
位于 /.claude/commands/triage-issue.md 的分类命令有严格的约束:
- 只能添加或删除标签——不得评论,不得编辑 Issue
- 只能使用已有的标签——先获取标签列表,绝不自创新标签
- 对生命周期标签保持保守态度——"漏打标签比打错标签代价更小"
- 对新 Issue 和评论采用不同的处理逻辑——新 Issue 进行全面分类;评论只触发生命周期标签更新
该命令会区分 issues 事件(完整分类)和 issue_comment 事件(生命周期标签管理)。在评论场景下,它会移除 stale/autoclose 标签(新活动意味着 Issue 仍然存活),并判断是否应移除 needs-repro/needs-info 标签(即所需信息已被提供)。
flowchart TD
EVENT{"Event type?"} --> NEW["issues (new issue)"]
EVENT --> COMMENT["issue_comment"]
NEW --> LABELS["Fetch available labels"]
LABELS --> READ["Read issue details"]
READ --> VALID{"About<br/>Claude Code?"}
VALID -->|No| INVALID["Label: invalid"]
VALID -->|Yes| CATEGORIZE["Apply category labels<br/>(type, area, platform)"]
CATEGORIZE --> DUPECHECK["Search for duplicates"]
DUPECHECK --> LIFECYCLE["Evaluate lifecycle labels<br/>(needs-repro, needs-info)"]
LIFECYCLE --> APPLY["Apply all labels"]
COMMENT --> READ_CONV["Read full conversation"]
READ_CONV --> STALE{"Has stale/<br/>autoclose?"}
STALE -->|Yes| REMOVE["Remove stale labels"]
STALE -->|No| NEEDS{"Has needs-repro/<br/>needs-info?"}
NEEDS -->|Yes| PROVIDED{"Info<br/>provided?"}
PROVIDED -->|Yes| REMOVE_NEEDS["Remove needs-* label"]
PROVIDED -->|No| KEEP["Keep label"]
style INVALID fill:#E53935,color:#fff
claude-dedupe-issues.yml(位于 /.github/workflows/claude-dedupe-issues.yml)使用 Sonnet 执行 /dedupe 命令。位于 /.claude/commands/dedupe.md 的去重命令会启动 5 个并行代理进行多样化搜索——每个代理使用不同的关键词和策略来寻找潜在的重复 Issue。最终由一个过滤代理消除误报,再通过评论脚本将结果发出。该工作流还会将事件记录到 Statsig 用于数据分析。
提示: 分类命令的工具限制(
Bash(./scripts/gh.sh:*)和Bash(./scripts/edit-issue-labels.sh:*))体现了与第 2 篇allowed-tools相同的设计模式——将 AI 代理约束在特定的、可审计的操作范围内。在公开仓库上运行 AI 时,这一点至关重要。
带宽限期的自动关闭
位于 scripts/auto-close-duplicates.ts 的自动关闭脚本实现了一套精细的宽限期机制。当去重命令发出"疑似重复"评论后,倒计时便开始。三天后,脚本会判断该 Issue 是否应被关闭:
flowchart TD
START["Fetch open issues<br/>created >3 days ago"] --> SCAN["For each issue"]
SCAN --> DUPE{"Has bot 'possible<br/>duplicate' comment?"}
DUPE -->|No| SKIP["Skip"]
DUPE -->|Yes| AGE{"Comment older<br/>than 3 days?"}
AGE -->|No| SKIP
AGE -->|Yes| ACTIVITY{"Any comments<br/>after dupe comment?"}
ACTIVITY -->|Yes| SKIP
ACTIVITY -->|No| REACTION{"Author gave 👎<br/>on dupe comment?"}
REACTION -->|Yes| SKIP
REACTION -->|No| EXTRACT["Extract duplicate<br/>issue number"]
EXTRACT --> CLOSE["Close as duplicate"]
style CLOSE fill:#E53935,color:#fff
style SKIP fill:#4CAF50,color:#fff
作者否决机制是这里的关键设计细节。脚本会专门检查 Issue 作者是否在重复检测评论上点了踩(通过 reaction.user.id === issue.user.id 进行匹配):
scripts/auto-close-duplicates.ts#L228-L241
如果作者提出异议,Issue 将保持开放。如果三天内没有回应,Issue 将被关闭,同时附上说明并邀请作者重新开启。
@claude 提及处理器
claude.yml 工作流允许用户直接在 GitHub 的 Issue 和 PR 中用自然语言与 Claude Code 交互。当 @claude 出现在 Issue 评论、PR review 评论、PR review 正文或 Issue 正文中时,该工作流就会触发:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
...
这使得每个 Issue 和 PR 都成为与 Claude Code 交互的入口。该工作流使用带有只读权限的 anthropics/claude-code-action@v1 和 Sonnet——它可以读取代码并给出回应,但无法推送变更。
sequenceDiagram
participant U as User
participant GH as GitHub
participant WF as claude.yml
participant CC as Claude Code Action
U->>GH: Comment: "@claude explain this error"
GH->>WF: issue_comment event
WF->>WF: Check: contains '@claude'?
WF->>CC: Run claude-code-action
CC->>GH: Read repository context
CC->>GH: Post response comment
安全沙箱:三层防御
在公开 GitHub 仓库上运行 AI 代理需要认真考量安全问题。claude-code 仓库实现了三层防御体系:
第一层:gh.sh — 沙箱化 CLI 包装器
位于 scripts/gh.sh 的脚本将 gh CLI 限制为四个以读取为主的子命令:
case "$CMD" in
"issue view"|"issue list"|"search issues"|"label list")
;;
*)
echo "Error: only 'issue view', 'issue list', 'search issues', 'label list' are allowed"
exit 1
;;
esac
参数 flag 采用白名单机制(--comments、--state、--limit、--label)。搜索命令明确屏蔽了 repo:、org: 和 user: 限定符,以防止跨仓库访问。Issue 编号必须为纯数字。仓库始终通过 GH_REPO 限定范围,无法跳转到其他仓库。
这是纵深防御的体现:即使 Claude Code 生成了意外的 gh 命令,包装器也会将其拒绝。
第二层:DevContainer 防火墙
位于 .devcontainer/devcontainer.json 的 DevContainer 配置需要 NET_ADMIN 和 NET_RAW 能力:
"runArgs": [
"--cap-add=NET_ADMIN",
"--cap-add=NET_RAW"
]
这些能力让位于 .devcontainer/init-firewall.sh 的防火墙脚本得以配置 iptables 规则。防火墙采用默认拒绝策略:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
然后有选择地放行以下流量:
- GitHub(IP 从
api.github.com/meta获取) - npm registry(
registry.npmjs.org) - Anthropic API(
api.anthropic.com) - Statsig(
statsig.anthropic.com、statsig.com) - VS Code 插件市场和更新服务器
- Sentry 错误上报
防火墙还会进行自我验证:确认 example.com 不可达,且 api.github.com 可达。任一检查失败,脚本都会报错退出。
第三层:企业级托管配置
位于 examples/settings/ 的示例展示了组织如何在策略层面锁定 Claude Code。
flowchart TD
subgraph "Layer 1: CLI Wrapper"
GH["gh.sh<br/>4 allowed subcommands<br/>Flag allowlist<br/>Injection prevention"]
end
subgraph "Layer 2: Network Firewall"
FW["init-firewall.sh<br/>Default-deny iptables<br/>Allowlisted domains only<br/>Self-verification"]
end
subgraph "Layer 3: Enterprise Settings"
ES["managed-settings.json<br/>Permission lockdown<br/>Hook restrictions<br/>Tool deny lists"]
end
GH --> FW --> ES
style GH fill:#4CAF50,color:#fff
style FW fill:#2196F3,color:#fff
style ES fill:#9C27B0,color:#fff
企业配置档案
仓库提供了三种部署档案,展示了从宽松到严格的逐级锁定方式:
| 配置项 | 宽松 | 严格 | Bash 沙箱 |
|---|---|---|---|
禁用 --dangerously-skip-permissions |
✅ | ✅ | |
| 屏蔽插件市场 | ✅ | ✅ | |
| 屏蔽用户/项目权限规则 | ✅ | ✅ | |
| 屏蔽用户/项目 hook | ✅ | ||
| 拒绝 WebSearch/WebFetch 工具 | ✅ | ||
| Bash 需要审批 | ✅ | ||
| Bash 必须在沙箱中运行 | ✅ |
位于 examples/settings/settings-strict.json 的严格档案展示了完整的锁定配置:
{
"permissions": {
"disableBypassPermissionsMode": "disable",
"ask": ["Bash"],
"deny": ["WebSearch", "WebFetch"]
},
"allowManagedPermissionRulesOnly": true,
"allowManagedHooksOnly": true,
"strictKnownMarketplaces": []
}
各关键属性说明:
allowManagedPermissionRulesOnly:忽略用户和项目级别的权限规则,只有企业托管的规则生效allowManagedHooksOnly:屏蔽来自用户配置和项目配置的 hookstrictKnownMarketplaces: []:空数组屏蔽所有插件市场disableBypassPermissionsMode: "disable":阻止用户使用--dangerously-skip-permissions运行
bash-sandbox 档案中的沙箱配置限制更为彻底,可以限定 bash 命令能访问的域名,并屏蔽本地 socket 访问。
提示: 这些配置文件可以在配置层级的任何位置生效,但
strictKnownMarketplaces、allowManagedHooksOnly和allowManagedPermissionRulesOnly等属性只有在企业配置中才会起作用。建议先将其应用到managed-settings.json进行本地测试,再部署到整个组织。
过期清理(Staleness Sweep)
位于 scripts/sweep.ts 的清理脚本按计划运行,负责两件事:将不活跃的 Issue 标记为过期,以及关闭生命周期标签已到期的 Issue。
markStale 函数会分页遍历所有开放的 Issue,按更新时间排序(最旧的优先)。它会跳过 PR、已锁定的 Issue、已分配的 Issue、已标记为过期的 Issue,以及点赞数不低于 10 的 Issue。其余所有在过期时间窗口内未更新的 Issue 都会被打上标签。
closeExpired 函数会遍历每个生命周期标签,找出标签添加时间已超出超时限制的 Issue,并检查标签添加之后是否有真人活动。这是最后的安全网——如果真实用户(非机器人)在标签添加后留下了评论,即便脚本本应关闭该 Issue,它也会被保留:
脚本还支持 --dry-run 模式,只记录将会发生的操作而不实际执行——这对于测试生命周期配置变更来说必不可少。
系列总结
在这五篇文章中,我们从高层架构出发,逐步深入到插件组件模型、多代理编排模式、hook 实现,最后到 GitHub 自动化基础设施,完整梳理了 anthropics/claude-code 仓库的全貌。这个仓库是扩展 Claude Code、以及用 Claude Code 管理自身的参考实现。
我们所探讨的这些模式——约定优于配置的插件发现机制、适配不同模型层级的代理设计、fail-open 与 fail-closed 的 hook 哲学、分层安全沙箱——不仅适用于这个特定的仓库,更是任何团队将 AI 代理融入开发工作流的通用构建模块。
如果你正在开发 Claude Code 插件,从 plugin-dev skill 的文档入手是最好的选择。如果你正在规划企业级部署,从 settings 示例开始。如果你对在 GitHub 上大规模运行 AI 代理感到好奇,这里的自动化基础设施就是目前最好的参考实现。