Skip to content

P1: /api/issues/reconcile merged PR detection misses body-keyword issue references #302

Description

@itsmiso-ai

Summary

POST /api/issues/reconcile fetches both open and closed PRs, and reconcileIssue() correctly handles merged-PR-to-issue closure — but the caller in src/app/api/issues/reconcile/route.ts only populates the merged PR map using branch name patterns (/issue[-_/]?(\d+)/i). It never calls the existing extractFixingIssueNumbers() helper, which uses PR body keywords (Fixes #, Closes #, Resolves #) per lib/issue-reconciliation.ts line 12-17.

Impact

The merged-PR detection silently misses the vast majority of fix-issue references:

  • Workers (wishlist crons, Saffron normal-lane) write "Fixes #NNN" in PR bodies — not followed by issue-NNN in branch names
  • Branch-naming conventions are inconsistent across repos:
    • misospace/dispatch uses fix/issue-NNN-slug on some, feat/xxx on others
    • misospace/miso-chat uses fix/xxx with no issue number in branch name
    • misospace/windowstead uses feat/xxx with no issue number
  • So issues closed by merged PRs are never auto-closed by the reconcile endpoint, meaning Dispatch state for closed issues becomes stale until the sync pipeline catches up (which can take minutes)

Root Cause

In src/app/api/issues/reconcile/route.ts (lines ~89-100), the merged-PR-to-issue detection block is:

constmergedFixingIssues=newMap<number,typeofallPrs[number]>();for(const[,pr]ofmergedPrsMap){constbranch=pr.head?.ref??"";constmatch=branch.match(/issue[-_/]?(\d+)/i);if(match){constissueNum=parseInt(match[1],10);if(!isNaN(issueNum)){mergedFixingIssues.set(issueNum,pr);}}}

This only checks branch names. The existing extractFixingIssueNumbers() in src/lib/issue-reconciliation.ts already has the correct body-keyword regex:

constFIXING_PATTERNS=[/fix(?:es)?\s+#(\d+)/gi,/close[sd]?\s+#(\d+)/gi,/resolve[sd]?\s+#(\d+)/gi,];exportfunctionextractFixingIssueNumbers(body: string|null): number[]{if(!body)return[];constnumbers=newSet<number>();for(constpatternofFIXING_PATTERNS){constmatches=body.matchAll(pattern);for(constmatchofmatches){constnum=parseInt(match[1],10);if(!isNaN(num))numbers.add(num);}}returnArray.from(numbers);}

But it is never called from the reconcile route — dead code for this purpose.

Fix

Use body keyword scanning first, with branch name as a fallback, for each merged PR:

constmergedFixingIssues=newMap<number,typeofallPrs[number]>();for(const[,pr]ofmergedPrsMap){// 1. Check PR body for keyword references (Fixes #, Closes #, Resolves #)constbodyNumbers=extractFixingIssueNumbers(pr.body??pr.title??null);for(constnumofbodyNumbers){if(!mergedFixingIssues.has(num)){mergedFixingIssues.set(num,pr);}}// 2. Fallback: check branch name patternif(!mergedFixingIssues.has(pr.number)){constbranch=pr.head?.ref??"";constmatch=branch.match(/issue[-_/]?(\d+)/i);if(match){constissueNum=parseInt(match[1],10);if(!isNaN(issueNum)){if(!mergedFixingIssues.has(issueNum)){mergedFixingIssues.set(issueNum,pr);}}}}}

Note

The GithubPR type already carries body: string | null (from src/types/index.ts). The fetchClosedPullRequests call already includes the "body" field in its JSON query (see src/lib/github.ts line 525 — --json number,title,body,...). So the body text is already available — it just isn't being inspected.

Labels

  • type/bug — reconcile should auto-close issues fixed by merged PRs, but body references are missed
  • priority/p1 — heartbeat no longer does this via gh, and Dispatch reconcile is the replacement
  • audit — root cause found during dispatch-workflow PR Renovate Dashboard 🤖 #14 audit of the offloaded pipeline
  • status/ready — groomed, fix is scoped and concrete
  • project/dispatch

Acceptance Criteria

  1. After fix, POST /api/issues/reconcile closes issues whose merged PRs reference "Fixes #NNN", "Closes #NNN", or "Resolves #NNN" in the PR body
  2. Branch name pattern still works as fallback (no regression)
  3. issuesClosed counter in response reflects actual GitHub issue closures
  4. Existing open-PR health check + label management continues working unchanged
  5. Audit log entries show reconcile_close_issue with reason "Fixed by merged PR #NNN (title)"

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    auditAudit, review, or investigation work.priority/p1High priority.status/doneWork is complete.type/bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions