feat: fix(create): check for existing PR before creating a duplicate card (#174) - #177
Conversation
There was a problem hiding this comment.
QA panel review — WARN
code-review-structural · head c485195b7980 · formal
[review-synthesizer completed: workflow code-review-structural:report]
PR #177 adds a PR-side dedup for issue cards (#174): before creating a card, _find_open_pr_for_issue scans open PRs referencing the issue via a single gh pr list --limit 100 call. Overall risk is low — the one surviving finding is a minor fail-open gap: in a repo with >100 open PRs, a referencing PR beyond the cap is silently missed and a duplicate card is created, exactly the incident #174 exists to prevent. Fix first: paginate the scan (or emit a warning when the cap binds). The panel showed no disagreement — the correctness finder flagged it and the cross-file and conventions finders independently noted it; the verifier confirmed the claim and evidence byte-for-byte against the diff's plus side, and nothing was refuted. Coverage gap: direct file reads 404'd on this repo, so verification grounded against the diff hunk rather than a full file read.
Findings
| Severity | Location | Finding | Verified | |
|---|---|---|---|---|
| 🟡 | minor | __init__.py:188 |
The new PR-side dedup scans at most the first 100 open PRs (gh pr list --limit 100, no pagination), so in a repo with more than 100 open PRs a PR referencing… |
confirmed |
findings JSON (machine-readable)
[
{
"file": "__init__.py",
"line": 188,
"severity": "minor",
"category": "correctness",
"claim": "The new PR-side dedup scans at most the first 100 open PRs (`gh pr list --limit 100`, no pagination), so in a repo with more than 100 open PRs a PR referencing the source issue is silently missed and a duplicate card is created — the exact incident #174 exists to prevent — with no warning; flagged by the correctness angle and independently noted by the cross-file and conventions finders.",
"evidence": " \"--limit\",\n \"100\",",
"verdict": "confirmed",
"note": "Evidence quote found VERBATIM on the plus side of the PR #177 diff for _find_open_pr_for_issue. Re-read the full command: single subprocess.run([\"gh\",\"pr\",\"list\",\"--repo\",slug,\"--state\",\"open\",\"--json\",\"number,title,body,url\",\"--limit\",\"100\"]) — no --page, no pagination loop, so at most 100 open PRs are scanned; a referencing PR beyond the cap yields None and creation proceeds silently (the only warning paths are returncode!=0 / exceptions, not the scan cap). Fail-open, gated behind force=false + source_issue, >100-open-PR repos only — minor is right. Line ~188 matches the added hunk; direct file reads 404'd on this repo, but the diff plus side is authoritative for this added code."
}
]There was a problem hiding this comment.
Promoting the WARN verdict for head c485195b7980: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- minor
__init__.py:188— The new PR-side dedup scans at most the first 100 open PRs (gh pr list --limit 100, no pagination), so in a repo with more than 100 open PRs a PR referencing the source issue is silently missed and
Approving a WARN does not resolve its findings (issue #22).
Summary
board_create_featurenow performs a mechanical PR-side dedup whensource_issueis set: it lists open PRs in the issue's own repo viagh pr listand scans each title/body for a word-bounded#Nor the full issue URL (so#12never matches#123). A hit refuses the card with the existing PR's number and URL and steers the caller to review/contribute there instead — preventing the incident where a card was created and a coder dispatched for an issue that already had an open, CI-passing PR. The check is best-effort with the same fail-open posture as the title dedup (any GitHub failure warns and never blocks creation), andforce=truebypasses it along with the existing checks. New tests cover the matcher, the gh seam (success, error exit, exception, unnormalizable ref), and the tool-level refusal/force/no-issue/failure paths.Fixes #174