fix(work-items): use GitHub computed close-linkage for open-linked-PR filter (#654) - #668
Conversation
… filter (#654) The GitHub adapter's "Open linked PRs" mechanic matched a closing-keyword jq regex over the raw PR body, so a fenced `Closes #<N>` example spuriously reported an open closing PR and dropped the still-pickable issue from the /work-items:work frontier. Replace the raw-body regex (and its partial gsub fence-stripper) with GitHub's own computed close-linkage via the GraphQL Issue.closedByPullRequestsReferences connection, filtered to open-state PRs. This excludes fenced code blocks and HTML comments, drops the word/number boundary guards, and honors the default-branch requirement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 10s —— View job PR Review —
|
`mis-drops` tripped the typos hygiene gate (`mis` -> `miss`/`mist`). Reword to `wrongly drops`; no semantic change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 14s —— View job
Review —
|
…e-filter rationale (#654) Review-bot findings on the open-linked-PR GraphQL mechanic: - Add the missing `| tr -d '\r'` to the final pipeline (repo Gotchas rule). - Document the `first:20` page bound and its cursor-pagination escape hatch. - Document that `select(.state=="OPEN")` is load-bearing, not redundant with `includeClosedPrs:false`: that arg suppresses only CLOSED (unmerged) PRs and still returns MERGED nodes (verified live against issue #487 / merged PR #629), so the OPEN filter is required to avoid a false in-flight report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 54s —— View job
Review — follow-up pass on
|
|
Thanks — addressed in
So the filter is load-bearing (an issue whose only closing PR merged to a non-default base, or that was reopened after a merge, must stay pickable). I kept it and documented the semantics inline instead of removing it. |
Post-green review classification3 review rounds, all findings resolved by the worker itself before this pass:
Verdict from the final review pass: "ready to merge." CI green (16/16). Flipping to ready — worker completed all fix rounds but the final draft→ready step wasn't executed. |
|
Claude finished @kyle-sexton's task in 1m 42s —— View job
Final Review —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 361a3009aa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…e page Address codex P2: because closedByPullRequestsReferences retains MERGED nodes, a long merge/reopen history can consume the first:20 page ahead of the currently-open closing PR, wrongly reporting an in-flight issue as pickable. Bump to the connection maximum (first:100, the GraphQL cap) and clarify that the page bound counts every ever-linked closing PR, not just open ones. Keeps the documented cursor-pagination escape hatch beyond 100. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 56s —— View job
Review — follow-up pass on
|
…ing PR past page 1 (#753) ## Summary The `work-items` github adapter's *Open linked PRs* signal (documented bash snippet in `plugins/work-items/tools/work-item-tracker/adapters/github/README.md`, § *Open linked PRs*) decided whether an issue already has an open closing PR — the signal `/work-items:work` uses to drop an in-flight candidate from the pickable frontier. It read a **single page** of `closedByPullRequestsReferences(first:100, includeClosedPrs:false)` and reported `true` iff any node was `OPEN`. Because `includeClosedPrs:false` suppresses only `CLOSED` (unmerged) PRs while retaining `MERGED` nodes, an issue with a long merge/reopen history (more than 100 linked closing PRs) could push its single `OPEN` closing PR onto a later page. The filter then saw an all-`MERGED` page, reported `false` = pickable, and the in-flight item could be re-picked from the frontier → double-dispatch (a duplicate PR). ## Fix Rewrite the documented snippet to walk the connection with cursor pagination — the root-cause fix called for in the issue, not a `first:100` bump (which only moves the boundary): - `gh api graphql --paginate` with an `$endCursor: String` variable and `pageInfo { hasNextPage endCursor }`, walking every page until GitHub reports no further pages. - `gh` applies `--jq` per page (each page emits its own `true`/`false`); `| tr -d '\r' | grep -qx true` collapses the per-page stream to one boolean — `true` as soon as any page carries an `OPEN` node. The connection exposes no server-side OPEN-state filter and no OPEN-first `orderBy` (args are `after`/`before`/`first`/`last`/`includeClosedPrs`/`orderBy`/`userLinkedOnly`), so pagination is the only correct route. The change is documentation-only: the query lives solely in the README (there is no compiled wrapper — `/work-items:work` runs the documented snippet directly), so no code was touched. The `gh api graphql --paginate` idiom matches the adapter's existing `--paginate` usage in `common.sh` and `reclaim.sh`. ## Verification The GraphQL `--paginate` contract (`$endCursor` variable + `pageInfo { hasNextPage endCursor }`, `--jq` applied per page) was confirmed against the official `gh api` manual, then the **exact rewritten pipeline** was executed live in the target environment (gh 2.95.0) against real repo issues: ``` #677 (open, no closing PR) -> false #654 (closing PR merged, none open) -> false #701, #546, #438, #376, #303, #263 -> true (each has an OPEN closing PR) ``` Emits exactly `true`/`false` in every case. Repo meta-gates run locally on the changed files, all green: ``` scripts/check-changelog-parity.sh --check -> EXIT 0 scripts/check-changelog-parity.sh --check-bump origin/main -> EXIT 0 markdownlint-cli2 README.md CHANGELOG.md -> 0 error(s) ``` The query lives only in the README, so the plugin's `*.test.sh` contract suite does not exercise this snippet (the github adapter `.test.sh` files that were run all pass); the live pipeline run above is the direct evidence. Closes #677 ## Related Refs #668 (the #654 fix that documented the `first:100` bound and flagged pagination as the deferred remedy), #654. --------- Co-authored-by: Claude Opus <noreply@anthropic.com>
Summary
The
work-itemsGitHub adapter's "Open linked PRs" mechanic (shipped in #463 / PR #643) decidedwhether an issue already had an open closing PR by running a closing-keyword
jqregex over theraw PR body. GitHub treats fenced code blocks and HTML comments as inert for auto-close
linkage, but the raw-body regex did not: a PR body carrying a fenced
Closes #<N>example matchedtrueand dropped the still-open, still-pickable issue#<N>from the/work-items:workfrontier. #643 shipped a partial
gsubfence-stripper alongside the regex, but it recognized onlyexactly-three backtick/tilde fences and silently missed four-or-more-backtick and indented fences —
exactly the fragile hand-roll #654 flagged as not-small.
Fix
Replace the entire raw-body heuristic (the
gh pr list --search "<N> in:body"prefilter, theclosing-keyword
jqtest, and the partialgsubfence-stripper) with GitHub's own computedclose-linkage, queried directly against the target issue number via the GraphQL
Issue.closedByPullRequestsReferencesconnection, keeping onlyOPEN-state PR nodes. This is theroot-cause fix #654 pointed toward: the signal is now GitHub's parsed issue linkage — the same set
GitHub renders in the issue sidebar and acts on at merge time — rather than a text match this
mechanic has to keep fence-, word-, and number-boundary-aware itself.
Consequences folded into the mechanic's rationale in the README:
maintain, and the fence-blindness work-items open-linked-PR filter: closing-keyword regex matches fenced-code-block examples #654 filed is closed.
#463cannot collide with#4630/#1463because the reference is GitHub's parsed linkage, not a regex over raw text.
only for a PR that targets the default branch. A
Closes #<N>on a non-default-base PR isignored by GitHub and now no longer excludes its issue — where the retired raw-body regex counted
it regardless of base. This is more correct per GitHub's real merge-time auto-close semantics.
Refs #<num>) stays intrinsic — a reference-without-closing never enters theclosing linkage, so it correctly does not exclude its issue.
Scope is held to the
work-itemsadapter. The sibling fence-blind matcher noted in #654 — thepre-create gate
KEYWORD_REGEXinplugins/source-control/skills/pull-request/reference/create.md— is not touched here: it is a different plugin, and it gates a PR body before the PR exists,
so it cannot use this issue-side GraphQL linkage. Whether it should adopt a different fence-aware
approach is left as a forward-reference follow-up, not folded into this adapter patch.
Verification
Empirical research proving
Issue.closedByPullRequestsReferencesis queryable viagh api graphqland returns GitHub's genuine auto-close linkage (open-state filtered) — not body text matches.
Schema exists and is queryable (live introspection,
gh api graphql):Positive cases — real prose closing keywords link, filtered to open PRs (the exact mechanic
this PR ships, run live against this repo):
Negative isolation — fenced and HTML-comment closing keywords are excluded. This PR body is
itself the live experiment. It carries a real
Closes #654in prose (positive control: an openissue closed via prose, in this draft PR, at this moment), plus two deliberately inert
fixtures — a closing keyword for open issue #644 only inside a fenced code block, and one for
open issue #667 only inside an HTML comment:
Because #644 and #667 are open issues (baselines
falseabove) and this same draft PR does link#654 from prose, the only difference for #644/#667 is that their keyword sits in a fence / comment.
Live query results against this PR are recorded below.
Live results (this PR is #668, base branch
main):Conclusion: within one PR body at one instant, GitHub linked the prose closing keyword (#654) and
excluded the identical keyword when it sat inside a fenced code block (#644) or an HTML comment
(#667). Since #644/#667 are open issues that this same draft PR would otherwise be capable of
linking, the fence / comment is the sole cause of exclusion. Both the fenced-example bug #654
reported and HTML-comment inertness are handled by
closedByPullRequestsReferenceswith noheuristic of our own.
Closes #654
Related
status: readyissues with an open PR stay pickable — no in-review state (re-pick risk) #463 / PR fix(work-items): exclude open-linked-PR issues from work frontier (#463) #643 — shipped the "Open linked PRs" mechanic (and its partialgsubfence-stripper)this PR replaces.
🤖 Generated with a Claude Code implementation subagent (issue #654)