📋 参考:标签触发工作流语法速查表

基于 sandcastlecourse-video-manager 提取的常用模式。 适用于快速查阅,不替代完整教程。配合 第 1 课第 2 课 使用。

触发事件
事件上下文
标签操作 (gh CLI)
标签状态机
并发控制
密钥与令牌
条件守卫
Shell 基础速览
常见陷阱

1. 触发事件

Issue 标签触发

最常用模式:当 Issue 被添加标签时触发。

on:
  issues:
    types: [labeled]

jobs:
  my-job:
    if: github.event.label.name == 'agent:hello'
    runs-on: ubuntu-latest

PR 标签触发(使用 pull_request_target)

sandcastle 使用 pull_request_target 而非 pull_request,因为后者在 PR 过时时可能无法生成 merge commit。

on:
  pull_request_target:
    types: [labeled]

jobs:
  my-job:
    if: github.event.label.name == 'agent:review'
    runs-on: ubuntu-latest
⚠️ pull_request vs pull_request_target pull_request_target 在 PR 的基础分支上下文中运行,因此可以访问 secrets。但它不会自动合并 PR 代码——你需要手动 checkout PR 的 head SHA。这两个仓库都在 checkout 步骤中显式使用 ref: ${{ github.event.pull_request.head.sha }}

其他触发事件

触发条件还可以组合使用。

# Issue 关闭时(用于 queued promotion 模式)
on:
  issues:
    types: [closed]

# 定时触发(cron)+ 手动触发
on:
  schedule:
    - cron: '0 9 * * 1-5'  # 工作日每天 9:00 UTC
  workflow_dispatch:        # 允许手动触发

2. 关键事件上下文变量

表达式类型说明
github.event.label.nameIssue / PR触发此工作流的标签名称
github.event.issue.numberIssueIssue 编号
github.event.issue.titleIssueIssue 标题
github.event.issue.bodyIssueIssue 正文(Markdown)
github.event.issue.labels.*.nameIssueIssue 上所有标签的名称数组
github.event.pull_request.numberPRPR 编号
github.event.pull_request.head.shaPRPR 分支的最新 commit SHA
github.event.pull_request.head.refPRPR 分支名称
github.event.pull_request.statePRPR 状态:openclosed
github.event.action通用触发事件的动作类型(如 labeledopened

3. 标签操作 (gh CLI)

⚠️ 前置要求:以下所有 gh 命令都要求 gh CLI 能识别当前仓库。在 GitHub Actions 中,要么先在之前的 step 中 uses: actions/checkout@v4(提供 .git 目录),要么设置环境变量 GH_REPO: ${{ github.repository }}。详见 第 1 课

添加标签

gh issue edit "$ISSUE_NUMBER" --add-label "agent:in-progress"

移除标签

gh issue edit "$ISSUE_NUMBER" --remove-label "agent:hello" || true
#                                             ↑
#                       || true 防止标签不存在时命令报错导致 workflow 失败

同时移除多个标签

gh issue edit "$ISSUE_NUMBER" \
  --remove-label "agent:implement" \
  --remove-label "agent:blocked"

PR 标签操作

# 语法与 issue 完全一致
gh pr edit "$PR_NUMBER" --add-label "agent:review"
gh pr edit "$PR_NUMBER" --remove-label "agent:in-progress"

发表 Issue 评论

gh issue comment "$ISSUE_NUMBER" --body "任务已完成。"

从文件发表评论(避免特殊字符问题)

gh issue comment "$ISSUE_NUMBER" --body-file "$RUNNER_TEMP/comment.md"

4. 标签状态机模式

这两个仓库使用一组标签来追踪工作流的状态。这是标准转换模板

用户添加触发标签 │ ▼ ┌─────────────────────────────────────┐ │ 1. 移除 trigger label │ │ 2. 移除 agent:blocked (如果存在) │ │ 3. 添加 agent:in-progress │ └─────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ 执行实际任务 │ └─────────────────────────────────────┘ │ ├── 成功 ──▶ 移除 agent:in-progress │ (可选) 添加下一个触发标签 │ └── 失败 ──▶ 添加 agent:blocked (always) 移除 agent:in-progress

工作流中的实现

jobs:
  my-job:
    # ...
    steps:
      - name: "Transition: start"
        run: |
          gh issue edit "$N" --remove-label "agent:hello" || true
          gh issue edit "$N" --remove-label "agent:blocked" || true
          gh issue edit "$N" --add-label "agent:in-progress"

      - name: "Do work"
        id: work
        run: ./do-the-thing.sh

      - name: "Transition: fail"
        if: failure() && steps.work.outcome == 'failure'
        run: |
          gh issue edit "$N" --add-label "agent:blocked"
          echo "FAILED" | gh issue comment "$N" --body-file -

      - name: "Transition: complete"
        if: always()
        run: gh issue edit "$N" --remove-label "agent:in-progress" || true

5. 并发控制

防止同一个 Issue/PR 同时运行多个工作流实例:

concurrency:
  group: agent-my-job-issue-${{ github.event.issue.number }}
  cancel-in-progress: false   # 不取消正在运行的,而是排队等待

关键点:sandcastle 和 course-video-manager 总是使用 cancel-in-progress: false。因为 AI 代理工作流可能运行数十分钟,取消它比等待更危险。

多个 PR 写入工作流共享一个并发组(防止同时推送):

concurrency:
  group: agent-mutate-pr-${{ github.event.pull_request.number }}
  cancel-in-progress: false

6. 密钥与令牌

令牌来源用途
secrets.GITHUB_TOKEN GitHub 自动提供 标准 API 操作。权限在 permissions: 块中声明
secrets.AGENT_PAT 手动添加的 Personal Access Token 触发下游工作流。用 GITHUB_TOKEN 添加的标签不会触发其他工作流
🔑 PAT 技巧:GitHub 会抑制由 GITHUB_TOKEN 触发的事件来防止递归。因此,如果工作流 A 想在完成时触发工作流 B,它必须使用 PAT 来操作标签。sandcastle 中实现为:先尝试 PAT,如果 PAT 不可用则回退到 GITHUB_TOKEN(此时下游不自动触发,需要手动帮助)。
# PAT 优先模式
- name: "Trigger downstream"
  run: |
    if [ -n "$AGENT_PAT" ]; then
      GH_TOKEN="$AGENT_PAT" gh pr edit "$PR" --add-label "agent:review"
    else
      gh pr edit "$PR" --add-label "agent:review"
      echo "::warning::No AGENT_PAT, agent:review must be applied manually"
    fi
  env:
    AGENT_PAT: ${{ secrets.AGENT_PAT }}
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

7. 常用条件表达式

# 标签匹配(最常用)
if: github.event.label.name == 'agent:hello'

# 与 GITHUB_TOKEN 触发的下游抑制无关——只是筛选标签

# 检查 PR 是否仍开放
if: github.event.pull_request.state == 'open'

# 检查 PR 是否关闭(拒绝关闭的 PR)
if: github.event.pull_request.state != 'open'

# 组合条件
if: |
  github.event.label.name == 'agent:implement' &&
  github.event.issue.state == 'open'

# 步骤条件函数
if: failure()         # 前面任何步骤失败时运行
if: success()         # 前面所有步骤成功时运行
if: always()          # 无论成功或失败都运行
if: cancelled()       # 工作流被取消时运行

8. Shell 基础速览

工作流的 run: 步骤在 Ubuntu 虚拟机中执行 shell 命令。以下是最常出现的语法:

语法含义示例
|| true 前一个命令失败时假装成功,继续执行 gh issue edit --remove-label "x" || true
$VAR 读取 shell 环境变量 echo "Issue #$N"
$(cmd) 命令替换:把 cmd 的输出嵌入字符串 echo "时间:$(date)"
${{ expr }} GitHub Actions 表达式,非 shell 语法 ${{ github.event.issue.number }}
env: 把 GitHub 上下文变量传给 shell N: ${{ github.event.issue.number }}
if [ "$X" = "Y" ]; then shell 条件判断(不是 Actions 的 if: if [ "$PROCEED" = "true" ]; then
echo "k=v" >> "$GITHUB_OUTPUT" 写入 step 输出,供后续 step/job 读取 echo "proceed=true" >> "$GITHUB_OUTPUT"
$RUNNER_TEMP 临时目录路径,工作流结束后自动清理 cat > "$RUNNER_TEMP/comment.md"

9. 常见陷阱

❌ 陷阱 1:标签未预先创建。工作流中的 gh issue edit --add-label 只能添加仓库中已存在的标签。如果标签不存在,命令会失败。解决:先手动在仓库 Labels 页面创建所有需要的标签。
❌ 陷阱 2:权限不足。如果工作流尝试操作 Issue 但缺少 issues: write 权限,gh 命令会静默失败或报 403。始终在 jobs.<job>.permissions 中显式声明所需权限。
❌ 陷阱 3:忘记 || true当标签可能已经被移除时,--remove-label 会因标签不存在而报错。始终添加 || true 除非你 100% 确定标签存在。
❌ 陷阱 4:递归触发。如果工作流 A 在结束时给 Issue 添加了一个标签,而该标签恰好是工作流 A 的触发标签,会形成无限循环。GitHub 部分防止了这种情况(GITHUB_TOKEN 事件不会触发下游),但 PAT 事件

← 第 1 课 | 第 2 课 →