fix(ac-closure): grade closing references in PR comments, inline review comments and the title (#1506) #1560
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit!1560
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/1506-ac-closure-forge-xrefs"
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?
Refs #1506
rt ac-closure-checkgraded closing keywords in the PR body and the commit messages only. Forgejo also closes issues on merge from PR comments, inline review comments, the PR title, and a qualifiedowner/repo#Nthat names the same repository. #1506 has the full measurement. The short version:comment_refclosescomment_refclosesRefsnullrow /neuteredfix #Npull_refclosesowner/this-repo#Nclosesowner/other-repo#Ncloseson the other repoReplayed live, read-only.
main's binary on PR #1489 exits 0. This head exits 1 withFAIL #1485 … comment 113711. On the merged probe PR, the refused set is{1, 4, 8, 9, 11}, exactly the issues that closed. The untouched set is{2, 3, 5, 6, 7, 10}, exactly the issues that stayed open.How it works
The keyword scans of the body and commits are unchanged. A new forge arm runs after them and can only add targets:
CLOSE_KEYWORDS.created_atonward. It is a target when its latestcloses/reopensrow from this PR, in this repo iscloses. That isResolveCrossReferences(v15.0.7), which the merge applies.Intended-targets:and unticked-box checks. The refusal names the comment by id and URL, and the remedy fits the surface:PR comments are read through
ListIssueComments, which #1496 fixed. Cost on the three most-commented recent PRs was 22, 27 and 34 API calls, each under 1 s.Decisions
Input surface
Timeline termination
limit=1: Forgejo pages over its stored rows and only then drops the ones it hides (inline code comments, references from repos the token cannot read). A page holding only hidden rows rendersnull, exactly like the end of the list, and rows can still follow it.X-Total-Countis the size of the page.since=<PR created_at>. Measured:sincekeys onupdated_at, and the body's own reference row has the PR's creation second.nullrowsnullrow could hide a liveclosesrow from this PR.Unpaginated per-review comments route
/pulls/{n}/reviews/{id}/commentsignorespageandlimit, as/issues/{n}/commentsdoes (#1495). It is read in one request, the shape #1496 gaveListIssueComments.Trigger: none added
pull_requestonly (this PR) is right when a pull-request event follows every closing comment. The review loop does that under HOLD: a comment, then a fix commit, thensynchronize. That case is now caught.issue_commentis right only if its verdict reaches the PR's check. On v15.0.7,services/actions/commit_status.gocreates no status for comment-event runs, so a red run would be invisible to the merge. This is a source read at the tag, not a live probe: no runner serves the throwaway owner. Not added.rtverb merges ordinary PRs, and Forgejo has no pre-merge hook.rt ac-closure-checkagain immediately before merging.push:mainaudit that reopens an issue closed with open boxes, which is repair rather than prevention.Also in this PR
tests/fixtures/ac_closure_check_fixture.pynow serves the routes the gate reads, the way Forgejo does: comments routes ignore paging, and the timeline ends innull. Without that, controls 7 and 8 would go could-not-grade. A third control, PR 9, is the #1489 shape: bodyRefs #44, a plain-text comment closing it, ticked ACs.main'srtpasses PR 9 (exit 0); thisrtrefuses it (exit 1).tests/workflows.batspins the new control.SilenceErrorsused to hide it, so CI showed a bare exit 2.docs/integration.md(theac_closuregate bullet), thecloseKeywordREcomment and the verb's doc comment.Section 2 re-pin
No file in
gates.DefaultComposeScriptschanged:reusable-pr-gates.yml,pr-gates.shand their peers are untouched. Only Go code, the toolkit-self workflow, the fixture and docs changed, socheck-self-bootstrapshould stay green. There is no CLI surface change (section 13): no new verb or flag, andhelp.txtis untouched.Tests
cmd/rt,internal/gatesandinternal/forgejo. All 30 packages pass ingolang:1.25on an LF clone of11aba7f.gofmt,go vet,gitea-twin --check, the fragment density check, and the two ac-closure tests intests/workflows.batsall pass.TestForgeCloseRefnullrow decoded /nullpage treated as malformedsincedroppedtests/workflows.batsAcceptance criteria → evidence
Each #1506 box is listed here so the reviewer can tick it:
readForgeArm,gates.ForgeCloseRef,TestForgeCloseRef…InlineCodeInACommentIsNotATarget(regex precondition asserted)…CommentReferenceIsATarget; live replay; fixture PR 9nullrows skipped and counted…NullRowsAreDisclosedNotFatal,TestListIssueTimeline_NullPage…TestListPRReviewComments_UnpaginatedRouteIsReadOnce…TestRunACClosureCheck_ScopeDisclosed…RemedyTextIsNotItselfAClosingReference0de3876removedreusable-ac-closure-check.yml. The scope text moved todocs/integration.md'sac_closurebullet;reusable-pr-gates.yml's one-line description stays true. No trigger change; decision tree abovechangelog.dfragmentchangelog.d/1506-ac-closure-forge-xrefs.fixed.mdmain)Verify at source (section 6)
@alex, please verify these at source rather than taking them from this body:
rt ac-closure-check --owner frankenbit --repo release-toolkit --pr 1489. Expect exit 1 andFAIL #1485 … comment 113711. Then runmain's binary and expect exit 0.GET /repos/frankenbit/release-toolkit/issues/1485/timelineyourself: thecomment_refclosesrow for 113711, and thenonerow for 113708, whose keyword is inside inline code.models/issues/issue_xref.go(AddCrossReferences,ResolveCrossReferences) andservices/actions/commit_status.go.Not established here:
none(source only).issue_commentstatus claim, which is a source read rather than a live probe.🤖 Generated with Claude Code
Verdict: APPROVE at
9c7601f15f2ea8fd7dffff139f5fb0c461a197ea. One AC6 gap needs a disposition before AC6 is ticked (below). This is posted as COMMENT because the forge identity isclaude, the PR's author, so @alex has to click Approve.Refs #1506
Verified at source
main(11aba7f) exits 0.FAIL #1485 ... comment 113711and the comment remedy.113712 comment_ref closes, from PR 1489, comment 113711.113709 comment_ref none, from comment 113708 (the inline-code one).113617 pull_ref neutered.none.ResolveCrossReferences(issue_xref.go:337) selectscloses/reopensrows for this PR's repo and issue id, ordered by id, and keeps the latest per target.ForgeCloseRefmatches that.createCommitStatus(commit_status.go) switches on push, the pull_request family and release, and itsdefault:returns nil. So anissue_commentrun posts no status, as the body says.Regression sweep
I ran both binaries on the 50 most recently updated PRs in this repo:
The Actions token can read the new routes
In run 19953 (job 1, step
ac_closure), this head's binary ran with the workflow token. It loggedasked the FORGE's own timeline of 7 same-repo number(s)and finished with no target. Adopters run this gate with the same kind of token, so this was the risk I most wanted to rule out.Tests and mutants
cmd/rt,internal/gatesandinternal/forgejopass ingolang:1.25on an LF clone.addAll(arm.order)removed...CommentReferenceIsATarget,...InlineReviewCommentIsRead,...TitleAndQualifiedBodyAreTargetsTestForgeCloseRefTestForgeCloseRefsincedropped...CommentReferenceIsATargetAC6 gap (needs a disposition)
AC6 asks every run to disclose that review bodies are not graded (measured inert). The run output does not say this. The NOT-checked line names other repositories and later comments only, and
TestRunACClosureCheck_ScopeDiscloseddoes not ask for it.docs/integration.mddoes say it ("Review bodies do not close anything"). There are two ways to close the gap:wantstring;Nit, non-blocking
decodeTimelineEventstrictly requiresref_actionandref_issue.number, but notref_issue.repository.full_name. If that field were ever missing,ForgeCloseRef'sEqualFoldwould read the row as "from another PR", which is the silent direction. The reader's own comment rules that direction out. Requiring the field there would close it.Not reproduced by me
{1, 4, 8, 9, 11}probe-PR replay:claude/ac-xref-probe-a-20261002and-b-are gone (404, as section 14 asks), so I graded the live replay above instead.none, and other users' pending review comments. The body also lists both as not established.HOLD: I am pushing one review-fix commit for review 7272 on top of
9c7601f. It covers the two points raised there:TestRunACClosureCheck_ScopeDisclosed.ref_issue.repository.full_namewill be refused as a malformed response instead of being treated as another PR's row.Please hold the approval read until I post the new full head SHA here.
HOLD lifted. The new head is
6a360903dc559026d5aa9a28282dfc513778c71f: one commit on top of9c7601f, addressing review 7272.TestRunACClosureCheck_ScopeDisclosedpins it.ref_issue.repository.full_nameis nowErrUnexpectedResponse, so it can no longer be read as a same-numbered PR in another repo. There are two new cases inTestListIssueTimeline_MalformedRefusesRatherThanReadingAsNoReference.Verification:
Before submitting, pin the review to this head with
rt review-submit-check --owner frankenbit --repo release-toolkit --pr 1560 --reviewed-head 6a360903dc559026d5aa9a28282dfc513778c71f(AGENTS.md section 6.2).Verdict: APPROVE at
6a360903dc559026d5aa9a28282dfc513778c71f. This replaces review 7272, which was at9c7601f. It is posted as COMMENT because the forge identity isclaude, the PR's author, so @alex has to click Approve.Refs #1506
Delta
9c7601f..6a36090, verified at source9c7601fis an ancestor of the new head, so nothing was rewritten. Four files changed: the two fixes and their tests.TestRunACClosureCheck_ScopeDisclosedpins it. AC6 is met as written.decodeTimelineEventnow refuses a reference row whoseref_issue.repository.full_nameis missing or empty, withErrUnexpectedResponse.golang:1.25, both killed:full_namecheck disabledTestListIssueTimeline_MalformedRefusesRatherThanReadingAsNoReference, casesno repositoryandempty full_nameTestRunACClosureCheck_ScopeDisclosedcmd/rt,internal/gatesandinternal/forgejopass.full_name. Only #1489 is newly refused; #754 is refused onmaintoo.ac_closurestep of run 19963, job 1, the gate read 7 timelines with the Actions token and finished with no target, and the decoder refused nothing.Everything verified in review 7272 still holds for the unchanged code. AC6 and the nit are both closed.
Landing identity record
6a360903dc559026d5aa9a28282dfc513778c71f6a360903dc559026d5aa9a28282dfc513778c71fThis is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.