fix(actions): reuse overlapping Lopu controller repair PRs - #591
Conversation
🔧 Lopu — the new title line can strand a pushed repair branch with no PRReviewed The defectThis PR introduces: report_title="$(sed -n 's/^# //p' "$report" | head -n 1 | tr -d '\r')"under What makes it more than cosmetic is where it aborts: git … push "HEAD:refs/heads/$branch" # ← branch is now on the remote
unset basic PUSH_TOKEN
report_title="$(sed … | head -n 1 …)" # ← abort lands here
url="$(gh pr create …)" # ← never runsThe outcome is a pushed Reproduced with the same The fixOne report_title="$(sed -n '/^# /{s/^# //;s/\r//g;p;q;}' "$report")"Same output, no pipeline to take a signal, and strictly less work. Same input: I also updated this PR's own contract to pin the fixed form and added a guard so assert.doesNotMatch(
reviewBlock,
/report_title=[^\n]*\|[^\n]*head -n 1/u,
"the diagnosis heading is read without a pipeline — a SIGPIPE there aborts the step
under pipefail, after the repair branch is pushed and before its PR is created",
);Both assertions are load-bearing: mutating the workflow back to the piped form What I verified and did not changeI drove the actual
The two that matter — a fork head and an unknown author — are both refused, so Two observations I left alone:
Validation
Checks at this head were fully green (17 pass / 23 skipping) and the CodeQL — Lopu 🌸 |
Lopu repository reviewLopu reviewed this PR against github-actions as Thingtime's principal PR and repository manager. Using Claude Opus 5. Lopu made justified improvements and pushed ef71202 to codex/lopu-pr-dedup. Lopu review — #591 ·
|
| candidate | outcome |
|---|---|
owner, lopu/workflow-check-fix-<n>, known title |
kept |
| owner, but a non-run-id branch name | dropped |
isCrossRepository: true |
dropped |
github-actions[bot], matched only by the body marker |
kept |
unknown author (mallory), otherwise perfect |
dropped |
The two that matter — a fork head and an unknown author — are both refused, so
the --force-with-lease push below can only ever target a same-repository
branch this repo's owner or its bot created.
Also verified: the lease is fenced to the inspected head (headRefOid), and
the fetch re-checks that SHA before anything is written, so a head that moved
during dedup is preserved rather than overwritten. Both failure reads
(gh pr list, pulls/N/files) exit 0 with outcome=dedupe-check-failed rather
than opening a possibly-duplicate PR. url=/outcome=none are pre-seeded at the
top of the step, so every early exit leaves a well-formed output for the
downstream "Publish Lopu's PR review updates" step.
Two observations I deliberately left alone:
- The loop
breaks on the first overlapping candidate (lowest PR number). If
that one then fails the merge or the 3-way apply, reuse is abandoned entirely
rather than trying the next overlapping PR. That is a defensible bound on
work per run, not a defect — worth knowing when reading a
a separate repair PR is requiredwarning. outcome=dedupe-check-faileddrops the repair for that pass entirely. That is
the intended fail-closed behaviour and it is announced as a::warning::, but
the diagnosis itself is not preserved anywhere, so the next pass has to
re-derive it.
Validation
| check | result |
|---|---|
resolve-pr-conflicts-routing-contract.mjs --self-test |
OK |
| mutation test: revert the yml line → contract must fail | fails as intended (exit 1) |
node --test resolve-pr-conflicts-routing-contract.test.mjs |
10 pass / 0 fail |
workflow-control-plane-contract.mjs --self-test |
OK |
node --check over every .github/scripts/*.mjs |
pass |
workflow YAML parses; bash -n on the extracted workflow_fix step |
pass |
| SIGPIPE reproduction, before vs after | 141 → 0 |
live merge into github-actions at 22bc5a0f |
clean |
Diagnosis
The controller-repair publisher only checked for an open PR whose head matched the branch it had just generated:
lopu/workflow-check-fix-${GITHUB_RUN_ID}Because every workflow run has a different ID, that lookup could never find a repair PR from an earlier run. The generic title then made distinct repairs look identical, while actual duplicate/overlapping repairs accumulated against
github-actions.The live backlog confirmed both failure modes: most similarly titled PRs contained distinct controller fixes, but #575 and #584 addressed the same two stale contract guards. The latter had stronger scoped regression coverage and superseded the former.
Fix
github-actionsbefore creating a branch.--force-with-lease.Regression coverage
The routing contract now pins the full behavior: repository-wide repair inventory, trusted ownership, complete file comparison, clean three-way composition, exact-head lease, explicit reused outcome, fail-closed read behavior, and diagnosis-derived new titles.
Validation
control-plane-ci.ymlverify command set: pass.resolve-pr-conflicts-routing-contract.mjs --self-test: pass.workflow-control-plane-contract.mjs --self-test: pass.promotion-worker-routing-contract.mjs: pass.run:block passesbash -n.git diff --check: clean.Existing backlog cleanup
Merged reviewed repairs #565, #573, #574, #577, #579, #580, #584, and #588 into
github-actions. Closed #575 as superseded by #584.