labels: state:needs-human is sticky and blind to mergeability, and nothing says which PR to merge next
#127
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#127
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?
state:needs-humanis sticky and blind to mergeability, so the board says "your turn" on PRs that cannot be merged — and there is no label that answers "which one do I merge next?"The bug
decide_state()in.github/scripts/labels-reconcile.shcomputes state from exactly three inputs: the draft flag, requested reviewers, and submitted review verdicts. It reads nothing aboutmergeable,mergeStateStatus, orstatusCheckRollup.And this line short-circuits everything:
Once the maintainer is requested, the PR reads
state:needs-humanpermanently. It stays there after the branch develops a conflict, after CI goes red, after a force-push staled every approval. Nothing demotes it.Live right now in this repo, and it is the second half that is currently visible:
MERGEABLE/CLEANstate:needs-humanMERGEABLE/CLEANstate:needs-humanMERGEABLE/CLEANstate:needs-humanThree PRs all saying "your turn", and nothing says which to merge first — despite order mattering, since they will conflict through
CHANGELOG.mdthe moment one lands.The stickiness half is not currently manifesting here only because nothing has conflicted yet. The code carries it identically:
grep -c 'mergeable\|mergeStateStatus\|statusCheckRollup' .github/scripts/labels-reconcile.shreturns 0, and theif requested "$HUMAN"short-circuit is atdecide_state()line 90, byte-identical to box's. The first merge here reproduces box's exact situation.Why this matters more than a wrong colour: the label is the only thing that makes the board scannable. A maintainer working from GitHub mobile — the realistic case, and the whole reason the label exists — has to open every PR to discover which are ready. The label does the opposite of its job.
It is the same failure shape as a stale approval: the state looks settled because nothing contradicted it.
Fix 1 — mergeability outranks the human request
A review request is an opinion about who should look next.
mergeableis a fact about the tree. Facts should win. Proposed precedence:The human short-circuit stays — it is right that an explicit request outranks the bot rounds — it just must not outrank "this branch does not merge."
Fix 2 — a state for "the agent owes a rebase"
state:addressingcurrently means the coding agent owes a reply and fixes to a review round. A conflicted branch also needs agent work, but it is a different job, triggered by a different event, and needs a different action. Folding them together loses that.Proposal:
state:needs-rebase— the branch does not merge (conflict, or a failing required check) and the agent owes a rebase or a fix. Distinct fromaddressing, which stays "owes a reply to reviewers".If you would rather not add a state, reusing
state:addressingis still strictly better than the status quo, because it at least stops claiming the maintainer is the blocker.Fix 3 —
merge-next, so the board answers the actual questionEven with the above correct,
state:needs-humanon three PRs does not say which one to merge first. Ordering matters here: these PRs conflict with each other throughCHANGELOG.mdandtest/cli.sh, so merging out of order costs a rebase every time.Proposal: a
merge-nextlabel on exactly one PR — the head of the merge queue.Unlike
state:*, this cannot be derived from GitHub's facts: queue order is intent. So it follows theblocked/releaseconvention in LABELS.md — set by a human or by the agent maintaining the queue, and never guessed by the reconciler. The reconciler's only job would be to clear it when the PR merges or stops being mergeable, so it cannot go stale the wayneeds-humandid.With all three, the mobile view becomes answerable at a glance:
merge-next+state:needs-human-> merge this onestate:needs-rebase-> agent is on it, ignorestate:bots-reviewing/state:addressing-> not your turnAcceptance
decide_state()readsmergeable/mergeStateStatusand the check rollupstate:needs-human, even with the human requestedstate:needs-rebase(or a documented decision to reusestate:addressing)merge-nextexists, is agent/human-set, and is cleared automatically when the PR merges or stops being mergeableCONFLICTINGmust NOT resolve tostate:needs-humanNote
box, rig and cast share this reconciler and this failure, byte for byte. Full write-ups filed in all three (heavy-duty/box#136) so none is a stub; whichever lands first is the reference implementation and the other two should follow it rather than diverge.
Siblings, same defect byte for byte: heavy-duty/box#136, heavy-duty/rig#87.