fix(ci): guard workflow triggers that are inert on dev until they reach main (#2814) - #2816
Conversation
…he divergence, record the accepted delays (#2814) `Issue Status on PR Merge` has not run since #2769 changed its trigger to `pull_request_target`: zero runs across six merges, three carrying a valid `Fixes #N`, every one of those issues left in `status-in-progress` until a human moved it. Not red — absent. GitHub executes the base branch's copy of a `pull_request_target` workflow but REGISTERS the trigger from the default branch, and `main` still says `pull_request`. Neither copy can start a run, and the change is inert until the next release cut. Decided (2026-09-15): wait for the release rather than hotfix `main`. This PR therefore ships the guard for the class, not the trigger: - tests/unit/test_2814_workflow_trigger_parity.py — for every workflow, each default-branch-registered event declared here must also be declared by `main`'s copy, or the delay is recorded in ACCEPTED_UNTIL_RELEASE with a reason. Reads `main` via `git show`, falls back to a `--depth=1` fetch (proven from a CI-shaped clone), skips loudly if unreachable. A stale allowlist entry fails too, so the record is pruned when the gap closes. First run found three MORE unrecorded divergences (`workflow_dispatch` on deploy-dev, alembic-head-watch, publish-images) beside the two `schedule` ones whose files already said "main only" in their own comments. - issue-status-on-merge.yml — the comment that said "runs the file as it exists on the BASE branch" now also says it is registered from `main`, since reading the first as the second is the inference that shipped this. - docs/memory/learnings.md — the class, stated once. Backfill (AC#2): #2730 (merged via #2731 at 15:15 UTC 2026-09-14) had never been promoted — now `status-in-dev`; #2794/#2795 had a stale `status-ready` beside their hand-added `status-in-dev` — stripped. Related to #2814 (AC#1 closes at the release cut, not here) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VpvcfgWkmQPD7DrDLmATTf
|
Mechanical, per the merge-train note on the PR. 1. Merged dev. `docs/memory/learnings.md` was the only conflict — the routine same-day append collision; both sides kept, dev's two entries first. 2. `_git()` passed `timeout=90` but caught nothing, so the #2019 fail-soft contract its docstring promises held for only one of the two network failure modes: a *refusing* network returns non-zero and the caller skips, but a *hanging* one raised TimeoutExpired out of the test and reddened the PR — the offline-runner case the helper exists to avoid. Now returns a synthetic non-zero CompletedProcess for TimeoutExpired and OSError (the latter covers a missing git binary). Verified both arms by injection. 3. The ACCEPTED_UNTIL_RELEASE comment said a stale entry "is not harmful, only untidy", directly contradicting test_every_accepted_entry_names_a_real_ divergence, which makes it a hard failure. The test is authoritative — the PR body's "the record prunes itself" is the decision — so the comment now says so, and names the expected post-release red on main's push CI. tests/unit/test_2814_workflow_trigger_parity.py: 30 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
merge-train: mechanical fixes were pushed to this branchCommit 1. 2. 3. The 4. Body:
Validation notes not fixed — yours to judge
Confirmed, and worth recordingThe guard was mutation-tested three ways during validation, including a novel un-allowlisted trigger — so it catches the class, not just the recorded instance. Dropping the Separately: 🤖 Generated with Claude Code |
vybe
left a comment
There was a problem hiding this comment.
merge-train 2026-09-15: lane C, /review + /cso --diff clean; _git fail-soft and the ACCEPTED_UNTIL_RELEASE comment fixed mechanically; 30 passed, all checks green.
Summary
Issue Status on PR Mergehas not run since #2769 changed its trigger topull_request_target— zero runs across six merges, three carrying a validFixes #N, every issue left instatus-in-progressuntil a human moved it. Not red: absent. GitHub executes the base branch's copy of apull_request_targetworkflow but registers the trigger from the default branch, andmainstill sayspull_request. Neither copy can start a run.Decision (2026-09-15): wait for the release cut rather than hotfix
main. So this PR ships the guard for the class, not the trigger — AC#1 closes whendev → mainlands, and the guard makes that delay a recorded decision instead of a silent one (AC#3).Changes
tests/unit/test_2814_workflow_trigger_parity.py— for every workflow, each default-branch-registered event (schedule,workflow_dispatch,repository_dispatch,workflow_run,pull_request_target,issues,release, …) declared on this branch must also be declared bymain's copy, or be inACCEPTED_UNTIL_RELEASEwith a reason.push/pull_requestare out of scope by design — they read the ref being built. A stale allowlist entry fails too, so the record prunes itself when the release closes the gap.mainviagit show origin/main:, falls back togit fetch --depth=1 origin main— proven from a fresh--depth=1clone with noorigin/main(CI's shape): fetched646c931, 30 passed. Skips loudly ifmainis unreachable rather than passing.issue-status-on-mergeentry → red; adding a bogus entry for a workflowmainalready has → red.scheduleones whose files already say "mainonly" in their own comments:workflow_dispatchondeploy-dev.yml(the manualno_cacherebuild),alembic-head-watch.yml,publish-images.yml. All six recorded with reasons..github/workflows/issue-status-on-merge.yml— comment only. "Runs the file as it exists on the BASE branch" now also says the trigger is registered frommain; reading the first as the second is the inference that shipped this.docs/memory/learnings.md— the class.Backfill (AC#2)
Every PR merged to
devsince 14:24 UTC 2026-09-14 with a same-repo closing keyword: #2799→#2794, #2798→#2795, #2797→#2742, #2769→#2767 (all already relabelled by hand), and #2731→#2730, which had never been promoted — nowstatus-in-dev. #2794/#2795 carried a stalestatus-readybeside the hand-added label — stripped.What is deliberately NOT here
The one-file hotfix to
mainthat would restore promotion today (the#2406precedent). Rejected in favour of the release cut; the allowlist entry names that decision and its date. Dual-trigger ondevwas also rejected —test_2767pins "both triggers would double-run the promotion".Test Plan
cd tests && pytest unit/test_2814_workflow_trigger_parity.py -v— 30 passedunit/test_2767_issue_status_fork_prs.py— 10 passed (comment edit only)git clone --depth=1with noorigin/mainissue-status-on-mergeallowlist entry (the guard's stale-entry test will demand it) and confirm aFixes #Nmerge produces a runFixes #2814
🤖 Generated with Claude Code
https://claude.ai/code/session_01VpvcfgWkmQPD7DrDLmATTf