Skip to content

bug: LINK_REGEX missing word boundary causes false pr_issue_links entries (bugfix/hotfix/discloses) #137

@dlhiccup

Description

@dlhiccup

Description

LINK_REGEX in src/lib/pr-linking.ts has no word boundary before the closing keywords (closes, fixes, resolves), so any word that ends in one of those keywords followed by #N creates a spurious row in pr_issue_links. Because the table uses additive INSERT OR IGNORE (links are never removed), the false entry persists across all future syncs.

Steps to Reproduce

  1. Open or sync a repo where a merged PR body or title contains any of:
    • "bugfix #<N>" or "hotfix #<N>" (matches fix #N)
    • "discloses #<N>" (matches closes #N)
    • "encloses #<N>" or "forecloses #<N>" (matches closes #N)
  2. Navigate to the Issues dashboard and filter by Completed.
  3. Observe that issue #<N> appears as "completed" (linked to the merged PR) even though the PR never intentionally closed that issue.

Expected Behavior

Only PRs that explicitly use a standalone closing keyword (Closes #N, Fixes #N, Resolves #N) should create an entry in pr_issue_links. Words containing a keyword as a suffix (bugfix, hotfix, discloses, etc.) should not match.

Actual Behavior

The regex in src/lib/pr-linking.ts:3 matches the keyword anywhere inside a word:

// current — no word boundary
const LINK_REGEX =
  /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s*(?:...)?#(\d+)/gi;

Examples of unintended matches:

PR body text Keyword matched Spurious link
bugfix #42 fix issue #42
hotfix #42 fix issue #42
discloses #42 closes issue #42
encloses #42 closes issue #42

Because pr_issue_links is append-only (INSERT OR IGNORE, refresh.ts:39), the false link is never cleaned up. The Completed filter (issues/route.ts:104) checks HAS_MERGED_PR_SQL against this table, so the affected issue is permanently misclassified.

Proposed Fix

Add \b (word boundary) before the keyword group:

// fixed
const LINK_REGEX =
  /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s*(?:(?:https?:\/\/github\.com\/)?([\w.-]+\/[\w.-]+))?#(\d+)/gi;

\b is a zero-width assertion — it matches the position between a non-word character (or start of string) and a word character. This correctly rejects bugfix #42 (the f in fix is preceded by g, a word char) while still matching Fixes #42 at a word start.

Environment

  • Browser: N/A (server-side data pipeline)
  • OS: N/A
  • Node version: any

Additional Context

  • Affected file: src/lib/pr-linking.ts line 3
  • Downstream consumers: src/lib/refresh.ts:29-46 (replacePrIssueLinks), src/app/api/issues/route.ts:31-34 (HAS_MERGED_PR_SQL)
  • The additive link-table design (refresh.ts comment, lines 22-28) means no self-correction occurs after a false link is written — a one-time data migration to delete known-spurious rows may be needed after the regex is patched.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions