fix: An epic-child PR's merge ref goes stale when the epic branch moves, and nothing retriggers it - #9379
Conversation
No preview deploy
|
|
governance: PASS @ 5114ac1 content:e3ced352a6cd — no contradiction, no weakening Scope
This is the governance-namespace derivation, not a control-plane classification -- whether this diff Corpus half -- does this contradict standing lawNo decision record is in the diff, so there was no subject to rank and Questions this change answers, and what standing law says about each:
Gate half -- does this quietly weaken a guard
VerdictPASS -- no contradiction with standing law and no weakening of a guard. |
|
review-code: FAIL @ 5114ac1 content:e3ced352a6cd — two repairs, both small Code-class slice: The five issue criteria are all met and CI is green. This FAIL is two repairs inside the verb's own CI at head
Per criterion
Findings — both blocking this round
Standing checks — all clean
Observation, not a findingMoving a child's head is what schedules the run, and it also changes that child's Deviations
VerdictFAIL — criteria 6 and 7 on #8880. Verdict-written: 2026-09-17T01:22:18Z |
|
review-doc: FAIL @ 5114ac1 content:e3ced352a6cd — one inaccurate exit-code promise Doc-class slice: one file, Hygiene checklist
Finding — blockingThe row's exit Observation, not a findingThe platform explanation — merge ref rebuilds, base push emits no event, re-run replays the stale sha DeviationsCovered in full in the VerdictFAIL — criterion 7 on #8880. Verdict-written: 2026-09-17T01:22:50Z |
|
review-skill: FAIL @ 5114ac1 content:e3ced352a6cd — no finding in this slice; the round is FAIL Skill-class slice: one file, Read this polarity correctly. I found nothing in the skill-class slice that refuses. Every check 1 — Behavioral correctness
2 — Trigger and description qualityUnchanged. The diff edits no frontmatter and adds no skill, so there is no routing surface to judge. 3 — Cross-skill conflict and shadowingUnchanged, and the block draws its own boundaries rather than absorbing a sibling's lane: publishing 4 — fabrika conventions
5 — Writing craft
Per criterion
VerdictFAIL as the round's polarity. No finding in the skill-class slice; nothing here to repair. Verdict-written: 2026-09-17T01:24:25Z |
|
Repair round 1 — both blocking findings addressed, plus the branch brought up to date with Finding 1 / criterion 6 — Finding 2 / criterion 7 — exit Both surfaces that state the exit codes were reconciled whole against the behaviour: the CLI long Also merged Green in this tree: Reviewer, start at — at eafac25 |
|
governance: PASS @ eafac25 content:8c751bc8cfc9 — no contradiction, no weakening Corpus half — does this contradict standing lawNo decision record is in this diff, so there was no subject to rank and Questions the change decides, written down before the read:
Read against the standing corpus, ids resolved through
No standing record was found that this diff contradicts. Gate half — does this quietly weaken a guard
No gate invariant is in this diff's reach, and nothing in it is removed or softened. Self fence
|
|
review-code: PASS @ eafac25 content:8c751bc8cfc9 — merge-ready Round 2. Both round-1 findings are repaired at this head, and the repair carries its own regression CI at head
Per criterion
Standing checks
Behaviour claims, traced
Deviations
Observation, not a findingThe sweep reports the first VerdictPASS — all seven criteria met, CI green with gate coverage at this head, deviations disclosed and Verdict-written: 2026-09-17T01:47:17Z |
|
review-doc: PASS @ eafac25 content:8c751bc8cfc9 — merge-ready Round 2. Doc-class slice: the one added row in The round-1 finding, repairedThe row previously promised exit Hygiene checklist
DeviationsBoth disclosed entries are graded in
Observation, not a findingThe exit-code semantics now live in three places: the CLI VerdictPASS — the round-1 inaccuracy is repaired, every hygiene line holds, and the portability guard is Verdict-written: 2026-09-17T01:47:54Z |
|
review-skill: PASS @ eafac25 content:8c751bc8cfc9 — merge-ready Round 2. Skill-class slice: the 32 added lines in 1 — Behavioural correctness
2 — Trigger and description quality[N/A] No frontmatter changed and no new skill was added. 3 — Cross-skill conflict and shadowing[PASS] No new skill, so no new trigger to collide. I checked the nearest lane: 4 — fabrika conventions
5 — Writing craft (
|
An epic run's child PR keeps reporting checks over the old base after the driver pushes the
assembly branch, and nothing in the pipeline retriggers it. This adds
fabrika lane retrigger <epic>and the operate step that runs it right after
lane push.What the platform actually does
The issue offered two readings and asked for the real one. I read it off a live pull request in this
repository rather than off the docs alone.
GitHub DOES rebuild
refs/pull/<n>/mergewhen the base moves. PR #8063's head had not beenpushed since 2026-09-06. Fetching its merge ref on 2026-09-16 gave commit
470decb9, whose firstparent is
2c2a5dc3— the tip ofmainthat morning, 374 commits past thebase.shathe PR payloadstill reports (
5c26458b). So the merge ref is current; the PR record's ownbase.shais the thingthat is frozen.
GitHub schedules nothing for a base push. Every check run on that same head (
e715358e) isstamped 2026-09-06, with hundreds of trunk commits landed since.
pull_requestfires onopened,synchronizeandreopened, and a base push is none of them(events that trigger workflows).
So the second reading holds: the ref rebuilds, the event never fires. That also rules out the cheap
lever the first reading would have allowed — a re-run replays the original event's
GITHUB_SHAandGITHUB_REF("The workflow will also use the sameGITHUB_SHA(commit SHA) andGITHUB_REF(gitref) of the original event that triggered the workflow run",
re-run workflows and jobs),
which is the stale merge commit and therefore the same red.
The verb
lane retrigger <epic>sweeps every OPEN pull request based onepic/<n>and moves the head of eachone that is behind the branch, through
PUT /pulls/{n}/update-branch— the base merged into the headbranch, which is a
synchronize, so a run is scheduled against a merge ref computed now. Nothing isclosed, nothing is force-pushed, no commit is rewritten, and the preview stage stays up. Close/reopen
is named forbidden in the operate step, with #8881 as the reason.
It is idempotent by construction: staleness is read from the platform's own comparison against the
base as it stands, so a child whose head already carries the base tip is reported
currentand neverwritten to. A second call right after a first writes nothing. Each write carries
expected_head_sha,so a head a sibling moved first refuses the write rather than misaddressing it; that 422 is shared
with a real conflict, and the two are split by re-reading the head.
Verdicts on exit 0 are
RETRIGGERED,CURRENTandNONE, above one row per child:#<pr> current,or
#<pr> <commits behind> behind, <head before> -> <head after>, where the count is thecomparison's own
behind_by— what says whether the base drifted by one commit or a hundred.Refusals:
8an accepted update whose head did not move inside its window, or a read that failedafter this sweep had already moved a child's head (UNKNOWN, re-read before writing again),
11alist or a comparison unread while the sweep had written to nothing, which is the sweep that is safe
to re-run,
42a child the assembly branch does not merge into, which is that child's repair roundrather than anything to retry here.
Live smoke against this repository:
lane retrigger 8716answersRETRIGGER-VERDICT: NONEat exit0, reading one page and writing nothing.
Start with
packages/fabrika-cli/src/lane/retrigger-verb.ts, then the operate step beside thelane pushfence.Deviations
forbidden in
operate/SKILL.mdwith Closing and reopening a PR tears down its preview stage mid-deploy and fails the deploy on a deleted D1 #8881 as the reason. Did: the skill names close/reopenforbidden and states the reason in words (it tears the PR's preview stage down mid-deploy); the
Closing and reopening a PR tears down its preview stage mid-deploy and fails the deploy on a deleted D1 #8881 citation lives in the
lane retriggerrow ofpackages/fabrika-cli/docs/verb-reference.mdand in this body. Why:
portability-guardreds a repository-specific issue reference insidethe shipped
claude-plugins/fabrika/corpus, and itsplugin-operaterow's ceiling only shrinks,so the number cannot land in that file. Disposition: stated here; the reason and the citation
are both reachable, in two files instead of one.
adapters to
src/io/pulls.ts(openPullsForBase,compareStanding,updatePullBranch).Why: the verb reads and writes over the GitHub API and this package's source may not invoke
gh, so those reads have nowhere else to live. Disposition: stated here.Fixes #8880