From 459b51399ec36cf2a7b40666a411911b5b72fcf5 Mon Sep 17 00:00:00 2001 From: Jonathan Zhang Date: Thu, 16 Jul 2026 00:31:56 -0700 Subject: [PATCH] fix(automerge): gate fleet CI templates (templates/ci-workflows/**) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The risk-tier regex knew .github/workflows/** (the rendered artifact) but not templates/ci-workflows/** (its SOURCE). Inverted blast radius: each repo's copy of pr-review.yml was gated on 45 repos, while the template feeding all 45 was not — so dotclaude#157 (which edited the caller template every repo inherits) auto-merged ~90s after opening, with no human review. dotclaude installs no pr-classify.yml, so .github/risk-paths.yml cannot gate it there; the global regex is the only lever. This is not a repo-specific quirk: the policy already treats .github/workflows as production infra, and this is the source of that file fleet-wide. scripts/ is the sharper half — merge-workflow-only-pr.sh drops branch protection and rotate-*-key.sh rotates fleet credentials. Mirrored per the lockstep contract: reusable regex + selftest mirror + shared corpus (4 RISKY). 3 SAFE counter-cases pin that the glob does not over-block neighbours (templates/email/**, templates/ci-workflows-notes.md, docs/ci-workflows/**). BB half ships in dotclaude (bb-automerge.py) — MERGE THAT FIRST; the corpus is shared, and test_bb_automerge_risk_patterns.sh is local-only (not in CI), so it goes red locally until both land. Verified: BB unpatched = 4 FAILs, patched = PASS 173/173. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/claude-author-automerge.yml | 1 + selftest/risk_patterns_corpus.txt | 18 ++++++++++++++++++ selftest/test_automerge_risk_patterns.sh | 1 + 3 files changed, 20 insertions(+) diff --git a/.github/workflows/claude-author-automerge.yml b/.github/workflows/claude-author-automerge.yml index 2d74c4c..af4b01e 100644 --- a/.github/workflows/claude-author-automerge.yml +++ b/.github/workflows/claude-author-automerge.yml @@ -490,6 +490,7 @@ jobs: ^docker-compose.*\.ya?ml$ (^|/)docker/docker-compose.*\.ya?ml$ ^\.github/workflows/.* + ^templates/ci-workflows/.* ^\.github/actions?/.* ^\.github/scripts/.* (^|/)action\.ya?ml$ diff --git a/selftest/risk_patterns_corpus.txt b/selftest/risk_patterns_corpus.txt index c83714e..e908a2e 100644 --- a/selftest/risk_patterns_corpus.txt +++ b/selftest/risk_patterns_corpus.txt @@ -91,6 +91,19 @@ RISKY: .github/workflows/deploy.yml RISKY: .github/workflows/lint.yml RISKY: .github/risk-paths.yml RISKY: .github/CODEOWNERS + +# ── RISKY: fleet CI templates (2026-07-16 gap — dotclaude#157) ───────────── +# The caller templates + fleet automation under templates/ci-workflows/ are the +# SOURCE of every repo's .github/workflows/*.yml. A Claude PR editing them +# self-merged in ~90s (dotclaude#157) because the regex only knew the rendered +# artifact, not its source — inverted blast radius: the rendered copy was gated +# on 45 repos while the template that feeds all 45 was not. dotclaude has no +# pr-classify, so risk-paths.yml can't gate it there; this is the only lever. +# scripts/ matters most: merge-workflow-only-pr.sh drops branch protection. +RISKY: templates/ci-workflows/callers/pr-review.yml +RISKY: templates/ci-workflows/callers/claude-author-automerge.yml +RISKY: templates/ci-workflows/scripts/merge-workflow-only-pr.sh +RISKY: templates/ci-workflows/scripts/rotate-anthropic-key.sh # BB-only: GH workflow doesn't include bitbucket-pipelines pattern (irrelevant to GH repos) RISKY_BB: bitbucket-pipelines.yml @@ -264,3 +277,8 @@ SAFE: .github/actionlint.yml # GitHub only reads dependabot config under .github/ — a root copy is inert SAFE: dependabot.yml SAFE: docs/dependabot-setup.md + +# ── SAFE: templates/ near-misses (the ci-workflows glob must not over-block) ── +SAFE: templates/email/welcome.html +SAFE: templates/ci-workflows-notes.md +SAFE: docs/ci-workflows/overview.md diff --git a/selftest/test_automerge_risk_patterns.sh b/selftest/test_automerge_risk_patterns.sh index 3d3ccc7..316e21c 100755 --- a/selftest/test_automerge_risk_patterns.sh +++ b/selftest/test_automerge_risk_patterns.sh @@ -26,6 +26,7 @@ patterns='^(.*/)?(auth|login|signin|signup|logout|session[s]?|oauth|oauth2|sso|j ^docker-compose.*\.ya?ml$ (^|/)docker/docker-compose.*\.ya?ml$ ^\.github/workflows/.* +^templates/ci-workflows/.* ^\.github/actions?/.* ^\.github/scripts/.* (^|/)action\.ya?ml$