Skip to content

fix(ci): docs 自动合并通道的两处盲区 —— 合并后下游看不见、拒绝后作者看不见 - #1987

Merged
Ikalus1988 merged 1 commit into
mainfrom
fix/docs-gate-visible
Sep 21, 2026
Merged

Ikalus1988 merged 1 commit into
mainfrom
fix/docs-gate-visible

Conversation

@Ikalus1988

@Ikalus1988 Ikalus1988 commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

User description

Fixes #1983.

缺口 1:合并之后,整个仓库看不见

gh pr merge 用的是 secrets.GITHUB_TOKEN。用 GITHUB_TOKEN 产生的推送不触发任何 workflow,所以这条通道合入的 PR,下游那串后处理全部静默跳过。

按 commit 数 push 事件创建的 run(2026-09-21 实测):

提交 怎么合的 push 触发的 run
804467880(#1969,本通道) github-actions[bot] / GITHUB_TOKEN 0
8417494c4(#1965) PAT 5
dc50c25ce(#1962) PAT 5

Release Please / Leaderboard Watch 最近 8 次运行全部对应 PAT 或人工 push,三个 GITHUB_TOKEN 提交一个都没有。

受影响:Release Please(release PR 不更新)、Leaderboard Watch、Build Live Feed、Update Badge Counts、Deploy Documentation、Lesson Security Scan、Setup Package CI、MCP Endpoint Stress Tests、PyPI Wheel Smoke、fatal-guard CI —— ≥10 个。站点部署不受影响(Cloudflare Workers Builds 是 Git 集成)。

改法:合并改用 secrets.SHELDON_PAT,并把"为什么不能是 GITHUB_TOKEN"与"为什么不需要 workflow scope"写进注释 —— 后者是刻意的:那个 scope 只对"修改 .github/workflows/ 的 PR"生效,而这条通道按自己的规则只合纯文档 PR;放宽它等于让自动合并的 PR 能改 CI 本身。

缺口 2:拒绝之后,作者什么都看不到

这个 workflow 一个评论步骤都没有(lessons 通道有 3 处),拒绝只存在于绿 run 里的一行 core.info。实测受害者 #1801:09-19 13:09–13:23 被打了 4 次 auto-merge-eligible,13:23 那次确实跑了并拒绝(Docs-only: false (3 files)),此后无运行、无留言 —— 作者那边"打了标签,然后什么都没发生",静坐 3 天。

改法:拒绝路径新增一步,upsert(发现已有 marker 就更新,不堆叠)一条评论,写明:判据(哪些文件破坏了 docs-only)、以及怎么让 gate 重看(移除再重打 auto-merge-eligible —— 这不是废话:gate 只在 labeled/unlabeled/edited/synchronize/ready_for_review 醒来,已经推过的 PR 不会自己重跑)。文件列表最多列 20 个。

验收对照(#1983)

  • 合并调用使用 PAT,注释写明为什么不能是 GITHUB_TOKEN、为什么不需要 workflow scope
  • 拒绝路径产生对外可见输出(PR 评论 upsert),含判据与下一步
  • 测试:断言 token 来源 + 拒绝评论逻辑;变体检验 4 条(改回 GITHUB_TOKEN / 放宽 workflow scope / 删掉拒绝步骤 / 只 create 不 update)
  • 用一次真实合并验证下游被触发 —— 这需要下一次该通道合并(本 PR 自己不是 docs-only,验证不了;下一次给它打标签的外部 docs PR 就会产生证据)

测试

判定逻辑写成两个对工作流文本求值的函数(merge_token_problems / refusal_report_problems),变异用例外加一份被改过的副本喂进同一个函数 —— "断言变异生效了"然后再断言一件恒真的事,那是装饰不是门禁。

python3 -m pytest tests/test_auto_merge_docs.py -q → 9 passed(原有 3 条 + 新增 6 条)。


PR Type

Bug fix, Tests


Description

  • Switch merge token from GITHUB_TOKEN to SHELDON_PAT so downstream workflows actually run

  • Add upserted refusal comment step when the docs-only rule fails

  • Surface the offending file list so authors see what to fix

  • Add mutation tests guarding both gaps against future regressions


Diagram Walkthrough

flowchart LR
  A["PR labeled auto-merge-eligible"] --> B["check: docs-only rule"]
  B -- "true" --> C["Enable auto-merge<br/>GH_TOKEN=SHELDON_PAT"]
  B -- "false" --> D["Report the refusal on the pull request<br/>(upsert comment with offenders)"]
  C --> E["push event → triggers downstream workflows"]
  D --> F["Author reads comment,<br/>fixes files, re-labels"]
  G["test_auto_merge_docs.py<br/>merge_token_problems / refusal_report_problems<br/>+ mutation tests"] -- guards --> B
  G -- guards --> C
  G -- guards --> D
Loading

File Walkthrough

Relevant files
Bug_fix
auto-merge-docs.yml
Switch merge to PAT and post upsert refusal comment           

.github/workflows/auto-merge-docs.yml

  • Replaces secrets.GITHUB_TOKEN with secrets.SHELDON_PAT on the Enable
    auto-merge step and documents why GITHUB_TOKEN (and widening to
    workflow scope) is forbidden
  • Adds offenders output to the check step so the refusal can list the
    files that broke the rule (capped at 20)
  • Adds a new Report the refusal on the pull request step that upserts
    (creates or updates) a single marker-tagged comment explaining the
    rule, the offending files, and how to re-trigger the gate
+80/-1   
Tests
test_auto_merge_docs.py
Add gap-closing and mutation tests for docs gate                 

tests/test_auto_merge_docs.py

  • Adds merge_token_problems and refusal_report_problems analyzers over
    the workflow YAML, plus a parametrized test that asserts both stay
    empty
  • Adds mutation tests that revert the PAT to GITHUB_TOKEN, widen the
    scope to workflow, drop the reporting step, or replace updateComment
    with createComment, asserting each is caught
  • Each mutation check first asserts the mutation actually changed the
    file, so a no-op mutation cannot falsely pass
+122/-0 

…without a word

Two gaps in the same file (issue #1983), failing in opposite directions.

**The merge was invisible to the rest of the repository.** `gh pr merge` used
`secrets.GITHUB_TOKEN`, and a merge made with `GITHUB_TOKEN` produces a push that starts
**no workflows**. Measured 2026-09-21 by counting push-triggered runs per commit:

    8044678  #1969, merged here (GITHUB_TOKEN)   → 0 runs
    8417494  #1965, merged with the PAT          → 5 runs
    dc50c25  #1962, merged with the PAT          → 5 runs

`Release Please` and `Leaderboard Watch`'s last eight runs all correspond to PAT or manual
pushes; none of the three `GITHUB_TOKEN` commits has one. At least ten workflows key on
`push` — Release Please, Leaderboard Watch, Build Live Feed, Update Badge Counts, Deploy
Documentation, Lesson Security Scan, Setup Package CI, MCP Endpoint Stress Tests, PyPI Wheel
Smoke, fatal-guard CI — so every PR merged through this channel landed without any of them
noticing. The repository has paid for this lesson twice (`auto-sync-prs.yml`, and
`lessons/contrib/ci-github-token-push-does-not-trigger-workflows.md`); this was the third
place. The token must **not** be widened to `workflow`: that scope is only needed for PRs
that modify `.github/workflows/`, and this channel merges docs only — granting it would let
an auto-merged PR change CI itself.

**The refusal was invisible to the person who opted in.** This workflow had no comment step
at all (the lesson channel has three), so a refusal existed as one `core.info` line inside a
green run. #1801 is the measured case: the maintainer applied `auto-merge-eligible` four
times on 2026-09-19, the 13:23 run executed and refused it (`Docs-only: false (3 files)`),
and nothing was posted and no run happened again — from the author's side, the label did
nothing for three days. There is now a step on the refusal path that upserts a comment
(capped at 20 offenders) naming the files that broke the rule and the way back in: re-apply
`auto-merge-eligible`, which is not busywork, because the gate only re-evaluates on
`labeled`/`unlabeled`/`edited`/`synchronize`/`ready_for_review`.

Both checks are functions over the workflow text and the mutation cases feed a mutated copy
through those same functions — asserting "the mutation took" and then asserting something
trivially true is decoration, not a guard.

Signed-off-by: Ikalus1988 <136884451+Ikalus1988@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Genius Analysis

  • Risk Level: medium_risk
  • PR Size: +202/-1 (203 lines, medium)
  • Impact: 2 files changed (.github, tests)
  • Rules: 8 core + 5 repo-specific

Checklist

  • ci_passing (PENDING) — checks are still running
  • dco_signoff (PASS) — all commits signed
  • tests_updated (PASS) — 1 test file(s) changed
  • issue_reference (PASS) — linked issue found

Anti-Patterns Detected

  • workflow_change (medium)

Suggestions

  • Verify workflow syntax with act or push to a test branch.

@cloudflare-workers-and-pages

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
misakanet-web a776f9e Commit Preview URL

Branch Preview URL
Sep 21 2026, 03:50 AM

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

1983 - Partially compliant

Compliant requirements:

  • auto-merge-docs.yml 合并调用使用 PAT(SHELDON_PAT),并在注释里写明"为什么这里不能是 GITHUB_TOKEN"与"为什么不需要 workflow scope"
  • 拒绝路径产生对外可见输出(PR 评论 upsert),含判据与下一步
  • 测试:断言该文件的 gh pr merge 不用 GITHUB_TOKEN
  • 测试:断言存在拒绝评论逻辑
  • 测试:变体检验(改回 GITHUB_TOKEN、删掉评论步骤 → 必须变红)

Non-compliant requirements:

  • (无)

Requires further human verification:

  • 用一次真实合并验证下游被触发(需要实际触发一次合并并检查 Actions UI 中 push 事件产生的 run 数 > 0)

1969 - Partially compliant

Compliant requirements:

  • (不适用)

Non-compliant requirements:

  • (不适用)

Requires further human verification:

  • (不适用)

1965 - Partially compliant

Compliant requirements:

  • (不适用)

Non-compliant requirements:

  • (不适用)

Requires further human verification:

  • (不适用)
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Upsert can stack on >100-comment PRs

The refusal-comment upsert calls github.rest.issues.listComments with per_page: 100 and no pagination, then finds the marker. On a PR with more than 100 comments the previous marker comment may not be on the first page, the find returns nothing, and the code falls through to createComment — producing a duplicate that the body text itself denies ("this comment is updated in place, not stacked"). Realistic only on long-lived PRs with >100 comments, but the gate explicitly markets itself as an upsert and the mutation test for stacking relies on it.

const { data: comments } = await github.rest.issues.listComments({
  owner: context.repo.owner, repo: context.repo.repo, issue_number, per_page: 100,
});
const previous = comments.find(c => (c.body || '').includes(marker));

@Ikalus1988

Copy link
Copy Markdown
Owner Author

🧾 Audit Report — PR #1987 (a776f9e)

📊 Quality Score

⚠️ Quality score unavailable; continuing with hard gates.

🔏 DCO Audit

✅ All commits signed-off.

📏 PR Size

Metric Value
Files Changed 2
Lines Added 202

🔐 Secret Scan

✅ No hardcoded secrets detected.

📦 Dependency Audit

⏭️ Skipped; no Python/JS dependency files changed.

🧪 Test Suite

✅ PASS — 54% coverage

📋 Lesson Schema

✅ All lessons valid.

⚖️ Verdict

✅ All gates passed. Ready for merge.


Scope: full | Triggered by a776f9e | View run

@Ikalus1988
Ikalus1988 merged commit a776f9e into main Sep 21, 2026
29 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 Merged — Thank you!

Your contribution has been merged into main.

PR: #1987 — fix(ci): docs 自动合并通道的两处盲区 —— 合并后下游看不见、拒绝后作者看不见

What's next:

  • Your code is now part of MisakaNet's failure-lesson corpus (now 407 lessons)
  • Feel free to pick up another issue labeled good first issue or status: competition
  • Questions? Ask in this thread or open a Discussion

Welcome to the MisakaNet contributor community! 🧠

@github-actions

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Paginate comment lookup to prevent upsert stacking

A single listComments call capped at per_page: 100 (the API maximum) defeats the
upsert: the gate wakes on labeled, unlabeled, edited, synchronize and
ready_for_review, so any PR that accumulates more than 100 unrelated comments
(reviewers, suggestion bots, prior gate runs, etc.) will push the original marker
comment out of the first page. The next evaluation then fails to find previous,
falls through to createComment, and stacks — exactly what the new step was added to
prevent. Paginate until the marker is found or the page comes back short.

.github/workflows/auto-merge-docs.yml [153-156]

-            const { data: comments } = await github.rest.issues.listComments({
-              owner: context.repo.owner, repo: context.repo.repo, issue_number, per_page: 100,
-            });
-            const previous = comments.find(c => (c.body || '').includes(marker));
+            let previous = null;
+            for (let page = 1; ; page++) {
+              const { data: comments } = await github.rest.issues.listComments({
+                owner: context.repo.owner, repo: context.repo.repo,
+                issue_number, per_page: 100, page,
+              });
+              if (comments.length === 0) break;
+              previous = comments.find(c => (c.body || '').includes(marker));
+              if (previous || comments.length < 100) break;
+            }
Suggestion importance[1-10]: 7

__

Why: The single listComments call with per_page: 100 is capped at the API maximum, meaning once a PR accumulates more than 100 comments the marker comment can fall off the first page. When that happens, previous is null, a fresh createComment runs, and the very stacking the step was added to prevent reappears. The improved pagination loop correctly terminates on an empty page, on a short final page, or once the marker is found.

Medium

@github-actions

Copy link
Copy Markdown
Contributor

✅ Merged! Thanks again, @Ikalus1988.

fix(ci): docs 自动合并通道的两处盲区 —— 合并后下游看不见、拒绝后作者看不见 (+202 lines, 2 files)

Quick question — did any MisakaNet lesson help you this time?
→ Share feedback

No need to reply if nothing comes to mind. ⚡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CI] 自动合并通道:合并之后没有事件,拒绝之后没有话(同一文件的两个缺口)

1 participant