fix(automerge): gate fleet CI templates (templates/ci-workflows/**)#117
Conversation
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 <[email protected]>
|
Risk class: This PR touches one of the blocked path categories from Auto-merge is refused by (This is a policy notice, not a code-quality failure. The classify job itself does not fail — required CI checks remain authoritative for "is the code green.") |
|
Coverage Floor — mode:
|
|
No issues found. Regex added in lockstep across workflow + selftest; corpus RISKY/SAFE cases verified (incl. the |
Closes an inverted blast radius in the risk-tier regex.
The gap
The regex knew
.github/workflows/**— the rendered artifact — but nottemplates/ci-workflows/**, its source. So each repo's copy ofpr-review.ymlwas gated across 45 repos, while the template that feeds all 45 was not.Not theoretical:
dotclaude#157edited the caller template every repo inherits and auto-merged ~90s after opening, unreviewed. I twice told the user it needed their click; the enforcement disagreed. Same class as the.github/dependabot.ymlgap (6 fleet PRs self-merged in ~90s, 2026-07-10): policy prose ≠ enforcement glob.Why the central regex, not risk-paths.yml
Repo-specific paths normally belong in a caller's
.github/risk-paths.yml(techrecon#405). That lever does not exist here: dotclaude installs nopr-classify.yml(noclassifycheck runs on it), so norisk:*label is ever applied and the global regex is the only gate.It's also not a repo quirk. The policy already treats
.github/workflowsas production infra; this is the source of that file fleet-wide — the same category, one level up.scripts/is the sharper half:merge-workflow-only-pr.shdrops branch protection,rotate-*-key.shrotates fleet credentials.Lockstep contract honored
.github/workflows/claude-author-automerge.yml^templates/ci-workflows/.*selftest/test_automerge_risk_patterns.shselftest/risk_patterns_corpus.txtRISKY, +3SAFE3 SAFE counter-cases pin that the glob does not over-block neighbours:
templates/email/welcome.html,templates/ci-workflows-notes.md,docs/ci-workflows/overview.md.Merge dotclaude#159 FIRST (the BB half). The corpus is shared; its 4 new
RISKY:entries assert againstbb-automerge.py, which lives in dotclaude.test_bb_automerge_risk_patterns.shresolves it from$HOME/.claude/..., so it is local-only and not run in CI — this repo's CI will stay green while a local BB run goes red.test_workflow_guards.pydeliberately excludes it.Verification
OK: all risk-pattern cases passbb-automerge.py: 4 FAILs (risky->safe) — drift caught exactly as the shared corpus intendsPASS — all 173 cases(was 166)Auto-merge rationale: MANUAL MERGE — fleet automerge policy enforcement;
topcoder1/ci-workflowsPRs are always manual by design.🤖 Generated with Claude Code