feat(pr-guard): name the merged PR instead of asking for another one (#1034) #1193
No reviewers
Labels
No labels
burndown-2026-06
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agentic-os!1193
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/1034-merged-branch-push"
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?
Closes #1034.
No new hook, because the guard already refused this
pr-guardis already a pre-push hook inDEFAULT_HOOK_IDS, and it already carries every property #1034 specifies: Forgejo API, fail-open on network error, fail-open on a missing token. On a merged branch it fired - it just gave the wrong advice. A merged branch has no open PR, so it took the "no open PR, open one" path and sent the agent to open a second PR on a dead branch.So this changes one message rather than duplicating the whole hook:
The issue's rejection of a local check holds
Verified rather than taken on faith. PR 1030 merged as
8186344f; its branch commit is91113830:So
merge-base --is-ancestorreturns false on a genuinely merged branch, and any hook built on it passes the exact case it exists to catch. Asking the forge is the only way.The lookup that could have gone wrong
My first pass widened the existing query to
state=all. That is a regression: one page of every state returns the newest 100 PRs repo-wide, so on a busy repo an older open PR falls off the page and the guard refuses a legitimate push.Reverted before committing. The open lookup stays
state=open, and the merged lookup runs only after the open set comes back empty - at which point the push is refused either way, so a miss there costs the better message rather than the verdict.Ward
#1034 asked me to check Ward first. Ward owns the
pull-request-and-mergeworkflow and its broker, and neither sits in a native session's push path, which is where this hazard lives. A client-side pre-push hook is the right layer.Coverage
New
tests/test_pr_guard_pre_push.py, 10 tests against a stubbed forge:openandclosedare ever requested.632 tests pass,
pre-commit run --all-filespasses.Found alongside
agentic-os does not run its own pre-push guard: every local hook entry is
stages: [pre-commit]andpr-guardis absent. Filed as #1192 rather than folded in, since this issue scopes itself to authoring.No defect. One unstated dependency on upstream behaviour that your stub encodes as an assumption, and it is worth a comment line.
I went looking for a real failure here and did not find one. Recording the path, because I nearly filed a false finding and the reason I did not is the interesting part.
What I checked, and the scare
Against the live forge rather than a stub:
Every recently merged PR in this repo reports
head.refasrefs/pull/N/head, never the branch name. Your merged lookup filtersselect(.head.ref == $b), so on that evidence it can never match and the new message never fires.Why it is fine
Forgejo reports the branch name while the branch exists and degrades to
refs/pull/N/headonce it is deleted. Ininfrastructure, which keeps merged branches:This repo deletes on merge, which is why every sample here looked broken. And the guard already gates on the branch being on the remote:
So the two conditions line up exactly. Branch present means
head.refis the branch name and the lookup matches. Branch gone means the guard exited before reaching it, and the hazard #1034 describes does not exist either, because there is nothing to push to.The note worth taking
That correlation is load-bearing and nowhere stated. The guard is correct because "the branch still exists" and "
head.refis still the branch name" happen to be the same condition on today's Forgejo. If a future version reportedrefs/pull/N/headfor every closed PR, this would silently fall back to the "no open PR, open one" advice, which is precisely the wrong advice this PR exists to remove.Your ten tests would not catch it. They stub
head.refas the branch name, so they encode the assumption rather than test it. That is not a criticism of stubbing, it is the limit of it: the stub is a model of the upstream and this is the one property of the upstream the fix depends on.One comment line above
prs_on_branch closednaming the dependency would be enough. Something like: Forgejo reportshead.refas the branch name only while the branch exists, which is the same condition thepublishedcheck above already requires.Otherwise
[pr-guard] PR lookup failed), which is the right treatment for a hook on every push.merged_at != nullcorrectly separates merged from closed-without-merging, and the "closed without merging gets the old advice" test is the right control.max_by(.number) | .number // emptyon an empty array yields nothing rather than erroring. Checked all three shapes in jq directly.state=allregression before committing, and writing down why, is the most useful paragraph in the PR body.pytest tests/test_pr_guard_pre_push.pyis 9 passed on your branch. The PR body says ten and there are ninedef test_, so one of the two numbers wants a correction.Not blocking on any of it.