Two PR-lifecycle facts learned the expensive way while driving #1393/#1316/#1318/#1322 to merge. Both are cheap to act on and both currently cost real work.
1. A stale PR branch does not need a rebase, and rebasing it costs a whole PR
A prior session's handoff recorded this as a hard constraint:
Force-pushing a rebased PR branch. git push --force-with-lease is denied by the auto-mode classifier, and so is the git reset --hard + merge-forward workaround. Consequence: every rebase of a PR branch costs a fresh branch + fresh PR + close the old one. This burned #1315 → #1377 → #1393.
The constraint is real but the consequence does not follow. git merge origin/main into the PR branch resolves the conflict and pushes fast-forward — no force-push, no classifier denial. main's ruleset is squash-only (allowed_merge_methods: ["squash"]), so the merge commits inside the PR branch collapse to one commit on merge and required_linear_history is satisfied.
Verified repeatedly this session: #1393 landed that way, and #1318 was merge-forwarded five times as main moved under it, never once needing a new branch.
The cost of not knowing this was #1315 → #1377 → #1393: two closed PRs, two abandoned branches, and every review thread on them re-opened from scratch on the successor.
Suggested: state it wherever the force-push block is documented, so the next reader gets the workaround with the constraint. The source-control plugin's PR-lifecycle guidance is the likely home.
2. gh pr view --json statusCheckRollup reports a running check with conclusion: "", not null
The obvious filter for "did anything fail" —
[.statusCheckRollup[] | select(.conclusion != null and .conclusion != "SUCCESS" ...)]
— matches every in-progress check, because an unfinished check has conclusion: "" (empty string), not null. I reported #1318 as having two failing checks when both were simply still running.
Correct forms:
# failures only
[.statusCheckRollup[] | select(.conclusion=="FAILURE" or .conclusion=="TIMED_OUT" or .conclusion=="CANCELLED")]
# still running
[.statusCheckRollup[] | select(.status!="COMPLETED")]
Related trap in the same area, worth pairing: a gate is not proven green by an empty failure list. On a freshly pushed head the rollup can be empty or partial, so absence of failures and absence of checks look identical. Assert presence of each required context — pr-title / pr-title, do-not-merge / do-not-merge, ci-status, security-review / security-review — with conclusion == SUCCESS, rather than inferring from an empty filter.
Suggested: fold both into whatever helper or documented recipe reads check state — babysit-prs' readiness gate is the obvious consumer.
Filed to make a gitignored .work/ handoff discardable; the findings are the durable part.
Two PR-lifecycle facts learned the expensive way while driving #1393/#1316/#1318/#1322 to merge. Both are cheap to act on and both currently cost real work.
1. A stale PR branch does not need a rebase, and rebasing it costs a whole PR
A prior session's handoff recorded this as a hard constraint:
The constraint is real but the consequence does not follow.
git merge origin/maininto the PR branch resolves the conflict and pushes fast-forward — no force-push, no classifier denial.main's ruleset is squash-only (allowed_merge_methods: ["squash"]), so the merge commits inside the PR branch collapse to one commit on merge andrequired_linear_historyis satisfied.Verified repeatedly this session: #1393 landed that way, and #1318 was merge-forwarded five times as
mainmoved under it, never once needing a new branch.The cost of not knowing this was #1315 → #1377 → #1393: two closed PRs, two abandoned branches, and every review thread on them re-opened from scratch on the successor.
Suggested: state it wherever the force-push block is documented, so the next reader gets the workaround with the constraint. The
source-controlplugin's PR-lifecycle guidance is the likely home.2.
gh pr view --json statusCheckRollupreports a running check withconclusion: "", notnullThe obvious filter for "did anything fail" —
— matches every in-progress check, because an unfinished check has
conclusion: ""(empty string), notnull. I reported #1318 as having two failing checks when both were simply still running.Correct forms:
Related trap in the same area, worth pairing: a gate is not proven green by an empty failure list. On a freshly pushed head the rollup can be empty or partial, so absence of failures and absence of checks look identical. Assert presence of each required context —
pr-title / pr-title,do-not-merge / do-not-merge,ci-status,security-review / security-review— withconclusion == SUCCESS, rather than inferring from an empty filter.Suggested: fold both into whatever helper or documented recipe reads check state —
babysit-prs' readiness gate is the obvious consumer.Filed to make a gitignored
.work/handoff discardable; the findings are the durable part.