Ivan's Blog
主题

GitHub Actions scheduling

GitHub Actions scheduling 表示仓库把 workflow 触发事件、并发 namespace、cancel-in-progress 和 required check 可见性组织成可审计的运行调度合同。目标不是让每个事件只跑一次,而是让当前 PR head 获得正确且可观察的 gate,同时不让无关 PR、main、deploy 或手工任务被同一个并发键误取消。

  • 运维
  • 工具

目录

Definition

GitHub Actions scheduling 表示仓库把 workflow 触发事件、并发 namespace、cancel-in-progress 和 required check 可见性组织成可审计的运行调度合同。目标不是让每个事件只跑一次,而是让当前 PR head 获得正确且可观察的 gate,同时不让无关 PR、main、deploy 或手工任务被同一个并发键误取消。

它处理的是“为什么该运行被取消、重跑或缺失”的问题:同一 PR 的新提交、标题或 base metadata 重验、review-state 更新、mainline、发布、部署和手工 dispatch 是不同运行类别,不能仅因它们都来自同一仓库就共享取消语义。它不替代 Quality gates 对 merge-readiness 的定义,不替代 PR label release 对发布意图的约束,也不把普通测试失败、workflow_call 复用或发布业务逻辑归入调度问题。

这里必须区分两种策略:普通、可替换的 PR candidate 或 revalidation 可以在其 per-PR namespace 中用 cancel-in-progress: true 丢弃过期工作;required gate 则必须确保每一次已经排队的评估都不会被后续事件抢占。后者用 queue: max 保留一个运行中 run 和最多 100 个 pending run,且不要与 cancel-in-progress: true 组合。GitHub 按各 run 开始等待并发组的时间 FIFO 处理;实际开始时间可能变化,所以不要把 dispatch 时间当成严格顺序保证(GitHub Actions concurrency documentation)。

When to use

当 .github/workflows/*.yml 同时包含 pull_request、pull_request_target、push、merge_group、workflow_dispatch 或路径过滤,并且使用 concurrency 时,应先检查本 Topic。特别是 workflow 监听 edited、多类事件共用 group、运行被标记为 cancelled,或 required check 在当前 head 上缺失时,不能直接把问题归因于测试失败。

使用前必须同时核验当前 workflow YAML、对应的 live run history、branch protection 或 ruleset 中的 required checks,以及仓库本地 workflow contract。只有确认触发器、group 与取消关系确实导致错误调度时才修改;若 job 已实际执行并因代码或环境失败,或 filter 本就应排除该改动,则分别按测试、运行环境或项目业务合同处理。

Habits / defaults

  • 先为每个 run 分类:新 candidate SHA、metadata revalidation、review-state 更新、mainline/release、deploy 和 manual dispatch。记录它的事件、head SHA、group、取消条件和 required-check 名称,再决定是否需要改 YAML(projects/octo-rill.md、projects/isolappurr-usb-hub.md、projects/display-ambient-light-desktop.md)。
  • workflow_run sidecar 的订阅 allowlist 也是 scheduling contract:expected-success workflow names 必须与实际 top-level name 精确一致,通知 workflow 自身要显式排除;失败判断绑定 conclusion == failure,不能把成功 skip 或 cancellation 当作 incident(projects/dockrev.md)。
  • 同一 PR 的 synchronize 可以按 PR 取消过期 candidate;metadata-only 的 edited 重验必须使用与 candidate 分离的 namespace,避免取消仍在执行的当前 head gate。pull_request_review.edited 是 review-state 事件,不能因同名 action 被误当成 PR metadata(projects/octo-rill.md、projects/codex-vibe-monitor.md)。
  • required PR completion gate 默认只监听 opened、synchronize、reopened 和 ready_for_review,排除 pull_request.edited。如果确实需要 metadata-only revalidation,应放入独立 workflow/namespace,显式防止 closed/merged PR 触发,并使用独立 check context;不要把 edited 直接加入 required completion gate。Dockrev 的 release-completion-pr.yml 与 test_release_workflow_contract.py 是 exact event-set contract 样本(projects/dockrev.md)。
  • 对 ordinary candidate/revalidation workflow,只有“新 run 完全取代旧 run”时才使用 per-PR cancel-in-progress: true。对 Label Gate、release completion 等 required gate,使用 per-PR queue: max 并省略或显式设为 false 的 cancel-in-progress;queue: max 与 cancel-in-progress: true 语义冲突,GitHub 会拒绝该 workflow(projects/flux-purr.md、GitHub Actions concurrency documentation)。
  • delayed required gate 在实际评估时必须用 runtime GitHub API 重新读取 PR labels,并把该 JSON snapshot 传给 validator;不能信任可能在排队期间过时的 github.event.pull_request.labels。对 pull_request_target,这个读取保持在 trusted base checkout 与最小只读权限边界内,不能为读取 labels checkout 或执行不可信 PR 内容(projects/flux-purr.md)。
  • 不同交付类别不能共享宽泛的可取消 group。PR preview 可以只在同一 PR 内取代旧 build;mainline、deploy、release 和手工 dispatch 必须有独立 namespace,并在没有可追溯补跑机制前保留在途工作(projects/isolappurr-usb-hub.md、projects/display-ambient-light-desktop.md)。
  • 检查 branch/path/event filters 和 required-check 名称是否仍能让当前 head 产生可见 gate。不得通过删除 required check、放宽保护规则或隐藏 workflow 来掩盖调度缺陷(projects/codex-vibe-monitor.md、projects/kaisoumail.md)。
  • pull_request_target 的 trusted base source、workflow permissions 和 checkout 边界是独立安全合同。隔离 metadata run 时只改 group 计算,不把未审查的 PR 内容带入有权限的执行路径(projects/octo-rill.md)。
  • rerun identity 必须保留 workflow_run.id 与 run_attempt,并让通知链接指向具体 run;不能只用 branch 或 head SHA 合并不同 attempt,否则维护者无法判断当前失败是否已被重跑(projects/dockrev.md)。
  • 本地 actionlint 1.7.12 尚不识别 queue: max。在该工具支持前,把 GitHub runtime 与官方文档视为语法权威,保留针对 workflow 文本的项目测试,并跟踪 actionlint #687;不得为了通过旧 linter 删除 required-gate 的非抢占队列。

Reference implementations

  • projects/octo-rill.md:ci.yml、label-gate.yml 和 review-policy.yml 同时展示 candidate、trusted metadata gate 与 review-state gate,是验证 edited 重验隔离规则的首个样本。
  • projects/codex-vibe-monitor.md、projects/kaisoumail.md 与 projects/proxy-broker.md:都把 PR candidate 的 edited 放进可取消 per-PR group,适合作为后续逐仓库取证对象,而不是直接复制 OctoRill 的 YAML。
  • projects/flux-purr.md:Validate PR labels 与 Release completion 是 per-PR 的 required gates;它们以 queue: max 非抢占排队、在 trusted base checkout 中读取当前 labels API snapshot,说明 required gate 不能沿用可取消 candidate 的策略。
  • projects/dockrev.md:release-completion-pr.yml 用 trusted pull_request_target checkout,并以 exact event set 排除 edited;项目 contract test 防止 required completion gate 在后合并元数据编辑时重新触发。
  • projects/isolappurr-usb-hub.md:Pages workflow 把 PR、merge_group 和 dispatch 放入全局 pages group,说明跨事件冲突与同 PR candidate 取消是不同问题。
  • projects/display-ambient-light-desktop.md:手工 release build 的可取消 workflow/ref group 说明 manual dispatch 需要单独判断,不应套用 PR 规则。

Failure modes

  • 把 edited 与 synchronize 放进同一可取消 group,会让 metadata 重验取消仍有价值的当前 head candidate,随后只剩迟到或缺失的 required check。
  • 把 pull_request.edited 直接加入 required completion workflow,会让 PR 合并或关闭后的元数据编辑重新产生 required gate,造成错误、迟到或无法解释的合并状态;默认排除 edited,确需重验时单独隔离其 workflow、namespace 和 check context。
  • 把 queue: max 与 cancel-in-progress: true 写在同一 concurrency block 会触发 GitHub workflow validation error;required gate 不能一边承诺 FIFO 等待、一边允许新 run 抢占在途工作。
  • required gate 从 event payload 读取 labels,会在排队期间遗漏已经发生的 label 变更;应在 job runtime 从 GitHub API 获取并消费同一份 snapshot。
  • 用全局 group 管理 PR build、main deploy 与 dispatch,会把不同风险等级的工作互相取消;反过来给所有 run 加唯一 group,又会丢失同 PR 新提交取代旧 candidate 的效率。
  • 只看 workflow 是否触发、不查 run history 与 required checks,容易把取消、filter 排除和真实测试失败混为一类,导致修复落在错误层面。
  • 只更新通知 workflow 的单个订阅项、不更新 checked-in allowlist 或 contract test,会让新增 expected-success workflow 静默漏报;应把 workflow classification、route 和 exclusion 同步维护(projects/dockrev.md)。
  • 为获得绿色状态而修改保护规则、required check、权限或 pull_request_target checkout,会破坏原有安全与 merge-readiness 合同;调度修复必须保持这些边界不变。
  • 将一个仓库的 group 命名直接复制到另一个仓库,会忽略不同 trigger、发布面和本地 contract;每次同步前都应重新取证并补足项目测试。
  • 为通过未支持语法的本地 linter 而移除 queue: max,会以工具兼容性换取 required gate 可被取消的回归;应保留 GitHub 官方文档、GitHub runtime 和仓库测试作为当前验证链路。

Evidence index

  • projects/codex-vibe-monitor.md
  • projects/kaisoumail.md
  • projects/octo-rill.md
  • projects/proxy-broker.md
  • projects/flux-purr.md
  • projects/dockrev.md
  • GitHub Actions concurrency documentation
  • actionlint issue #687
  • projects/isolappurr-usb-hub.md
  • projects/display-ambient-light-desktop.md
  • skills/style-delivery/SKILL.md