phase-merge: false CI timeout on a green PR — ci_deadline is inherited from an earlier gate, ci_gated_sha goes stale #433
Labels
No labels
bug
contract
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli#433
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
phase-mergecan report a 30-minute CI timeout without waiting, exit1, and writestatus: ci_failedonto a card whose PR head has a green pipeline. Two independent defectscombine to produce it.
Defect A —
ci_deadlineis inherited from an earlier gate and never reset for a new headCiGate.startOfWait(core/model/CiGate.scala:351-361) reconstructs the start of the wait from apersisted deadline on the card:
nowis used only when the field is absent or unparseable. So when an earlierphase-prwroteci_deadlineand then exited 3, aphase-mergerun later inherits that same deadline — and if theclock is already past it, the wait is born exhausted. The command reports
Timed out waiting for CI checks after 30mhaving waited seconds, regardless of what CI actuallysays about the current head.
Defect B —
ci_gated_shais only written on a successful gate, so the card-only shortcut never engagesisStateOnlyDelta(core/commands/PhaseMerge.scala:144-149) exists precisely to stop a card-onlycommit from costing a second CI cycle:
Its own docstring states the intent: "
phase-prgates the phase and then pushes its own card write,which starts a run nobody is waiting for. Waiting for it doubles the CI a phase costs."
But
ci_gated_shais written only when a gate succeeds. Whenphase-prkeeps exiting 3, thefield is never refreshed, goes stale by whole phases, and
diffNameOnlythen returns the entirephase changeset — so the mitigation can never fire, and every card write costs a full CI cycle.
Those cycles are what burn the deadline in Defect A.
Observed failure
Project
medeca-modul-poptavky, issue MEDECA-407, phase 08, GitLab MR !407, 2026-08-21../iw phase-prcreated the MR, waited, and exited 3:Waited 501s of 30m. Nothing failed and nothing needs a person.CI then went green: pipeline 5169 on
443d4a3aa—success.Card state was moved to
awaiting_reviewand pushed. Pipeline 5170 on the new head4214ca936(areview-state.json-only commit) — also success../iw phase-merge --max-retries 0returned in well under its own 30-minute budget with:exit
1, and left behind an unpushed commit plus a working-tree edit that between them wrotestatus: ci_pending→ci_failed.Merging by hand succeeded immediately:
glab mr merge 407 --squashprinted✓ Pipeline succeeded. ✓ Merged!The card fields at the time
ci_deadline2026-08-21T16:49:31.994Zphase-mergeran (~18:2x)ci_gated_sha411d6fe68git log -S'ci_gated_sha'on the card shows the value last changed back at phase 02 — it hadbeen stale for six phases, so
isStateOnlyDeltahad been dead that whole time.With
startOfWaitcomputingstartTime = 16:49:31 − 30m = 16:19:31, the wait was already ~2 hoursold at birth. Hence the instant "timed out after 30m".
Impact
ci_failed; the forge says the pipelinesucceeded.
invocation pushes another card commit, starting another pipeline, while the inherited deadline
stays expired. The consuming project recorded this livelock twice — once for
phase-prin phase 07(five pipelines, 5161-5165, four of which reported
successwhile the command reported a timeout),and again for
phase-mergein phase 08.which is exactly what the gate exists to avoid.
Suggested directions
Offered as starting points, not a prescription:
ci_deadlinewhen the head SHA under gate changes — a new commit is a newpipeline and deserves a fresh budget. Storing the wait's start keyed by head SHA, rather than an
absolute deadline, would make this structural.
ci_gated_shawhenever a gate observes green for a SHA, not only on the path that alsomerges and advances. That keeps the card-only shortcut alive across
exit 3waits, which is whenit is most needed.
an inherited budget is already spent, re-read CI once and report the real verdict instead of a
timeout.
CommitPolicy.CommitBeforeGate), orarranging that
review-state.json-only commits do not trigger a pipeline at all. The cheapest fixfor "the retry invalidates the run it is waiting for" is to stop creating the extra run.
Related
ci_deadline.batch-implementspending its retry budget onci_pending. Thisissue is about the deadline and gated-sha mechanism inside
phase-pr/phase-mergethemselves.