Parent: #1936
Problem
resolveLinkedIssueHardRule (src/review/linked-issue-hard-rules.ts:176) fetches each linked issue; a 404 (nonexistent issue) returns undefined and is filtered out. If all referenced issue numbers are nonexistent, issueFacts.length === 0 and the function returns undefined (no verdict) — which the planner reads as "no violation." Separately, the advisory missing_linked_issue finding (src/rules/advisory.ts:665) only checks pr.linkedIssues.length === 0, a pure regex match count with zero existence validation. So a PR body containing Fixes #999999 (a made-up number) passes the "has a linked issue" advisory check and is fully exempt from the ownerAssignedClose/missingPointLabelClose/maintainerOnlyLabelClose hard rules — even on a repo that has all three set to block.
This is a confirmed, deliberately-tested fail-open design (there's an existing test explicitly titled "is fail-open: undefined when every fetch fails") — but the codebase already recognized and closed the identical class of evasion for linked-issue overflow (a prior fix treats too-many-refs as "unsafe to verify → violation" rather than silently skipping), and that same principle was never applied to the all-404 case.
Failure scenario: a repo enables all three linked-issue hard rules specifically to keep contributors from claiming maintainer-reserved work. A contributor writes Fixes #99999 (nonexistent) instead of linking a real (and rule-violating) issue. The hard-rule evaluator never runs; the PR is treated as if it had no hard-rule violation and can auto-merge on green CI + passing gate, defeating the operator's intent.
Requirements
- A PR whose only linked-issue references are confirmed nonexistent (404, not a transient fetch error) must not be treated as "no violation" when hard rules are enabled.
- Must preserve fail-open behavior for genuine transient failures (network/5xx/rate-limit) — an outage must never spuriously close good PRs.
Deliverables
- Have
fetchLinkedIssueFacts return a tri-state result (found / not-found / fetch-error) instead of collapsing both 404 and transient errors into undefined.
- In
resolveLinkedIssueHardRule, when every linked issue number resolves to not-found (never a real issue) and at least one hard rule is block, treat it the same way the existing overflow case is treated — as an unsafe-to-verify violation — while continuing to fail open on genuine fetch-error.
- Consider hardening the
missing_linked_issue advisory finding similarly, so a fabricated issue number is flagged even when hard rules are off.
- Add a regression test asserting a PR whose only linked-issue reference 404s is treated as a violation when a hard rule is
block, and that a transient fetch error still fails open.
Acceptance criteria
- All-nonexistent linked-issue references trip the hard-rule violation when at least one rule is enabled in
block mode.
- A genuine GitHub API outage during issue-fact fetching still fails open (no spurious closes).
Expected outcome
A contributor can no longer defeat owner-assigned/missing-point-label/maintainer-only enforcement by citing a made-up issue number — closing the same class of verification-evasion the codebase already fixed for reference overflow.
Parent: #1936
Problem
resolveLinkedIssueHardRule(src/review/linked-issue-hard-rules.ts:176) fetches each linked issue; a 404 (nonexistent issue) returnsundefinedand is filtered out. If all referenced issue numbers are nonexistent,issueFacts.length === 0and the function returnsundefined(no verdict) — which the planner reads as "no violation." Separately, the advisorymissing_linked_issuefinding (src/rules/advisory.ts:665) only checkspr.linkedIssues.length === 0, a pure regex match count with zero existence validation. So a PR body containingFixes #999999(a made-up number) passes the "has a linked issue" advisory check and is fully exempt from theownerAssignedClose/missingPointLabelClose/maintainerOnlyLabelClosehard rules — even on a repo that has all three set toblock.This is a confirmed, deliberately-tested fail-open design (there's an existing test explicitly titled "is fail-open: undefined when every fetch fails") — but the codebase already recognized and closed the identical class of evasion for linked-issue overflow (a prior fix treats too-many-refs as "unsafe to verify → violation" rather than silently skipping), and that same principle was never applied to the all-404 case.
Failure scenario: a repo enables all three linked-issue hard rules specifically to keep contributors from claiming maintainer-reserved work. A contributor writes
Fixes #99999(nonexistent) instead of linking a real (and rule-violating) issue. The hard-rule evaluator never runs; the PR is treated as if it had no hard-rule violation and can auto-merge on green CI + passing gate, defeating the operator's intent.Requirements
Deliverables
fetchLinkedIssueFactsreturn a tri-state result (found/not-found/fetch-error) instead of collapsing both 404 and transient errors intoundefined.resolveLinkedIssueHardRule, when every linked issue number resolves tonot-found(never a real issue) and at least one hard rule isblock, treat it the same way the existing overflow case is treated — as an unsafe-to-verify violation — while continuing to fail open on genuinefetch-error.missing_linked_issueadvisory finding similarly, so a fabricated issue number is flagged even when hard rules are off.block, and that a transient fetch error still fails open.Acceptance criteria
blockmode.Expected outcome
A contributor can no longer defeat owner-assigned/missing-point-label/maintainer-only enforcement by citing a made-up issue number — closing the same class of verification-evasion the codebase already fixed for reference overflow.