Skip to content

feat(pr-followup): link a PR to its issue from commit messages when the body has none - #923

Merged
joryirving merged 1 commit into
mainfrom
feat/link-issue-from-commit-messages
Sep 2, 2026
Merged

feat(pr-followup): link a PR to its issue from commit messages when the body has none#923
joryirving merged 1 commit into
mainfrom
feat/link-issue-from-commit-messages

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

  • extractLinkedIssue falls back to the PR's commit messages when the title and body carry no issue reference.
  • Commits are fetched only on that fallback path, so the common case costs nothing extra.

Why

An unlinked PR drops out of follow-up completely: no linked-issue health, nothing enqueued, so a CHANGES_REQUESTED review on it is never acted on and the PR sits blocked with nothing watching it.

Two live examples where the link existed and we were not looking where it survived:

PR body reference commit message
misospace/pr-reviewer-action#535 none Fixes #534
misospace/llmkube-images#284 none Fixes #277

#535 has all checks green, CHANGES_REQUESTED, an empty PR-fix queue, and no workload — because nothing ever linked it to #534.

Matching rule

The body keeps its existing loose rule (first #NNN anywhere). Commits require a closing keyword — fixes/closes/resolves and their variants. A bare #123 in a commit message is as likely to reference a prior PR as to declare what this one fixes, and a wrong link is worse than no link.

Cost

One extra request to /pulls/N/commits, only when title and body yield nothing. Wrapped in a try/catch: an unlinked PR is the status quo, not a failure.

Verification

  • npx vitest run — 2493 passed, 5 skipped (6 new, covering body precedence, keyword variants, bare-reference rejection, first-match ordering, and empty/null commit lists)
  • typecheck, lint and build clean

https://claude.ai/code/session_01YSuDvZq9ncvyX85Uzx3cQh

…he body has none

extractLinkedIssue read only the PR title and body. A PR whose body carries no
reference is never linked, so it has no linked-issue health, is never enqueued
for a fix, and a CHANGES_REQUESTED review on it is never acted on — the PR sits
blocked indefinitely with nothing watching it.

That is not hypothetical: misospace/pr-reviewer-action#535 and
misospace/llmkube-images#284 both opened with an empty body while the commit
that did the work said 'Fixes #534' and 'Fixes #277'. The link existed; we were
not looking where it survived.

Fall back to the PR's commit messages, fetched only when title and body yield
nothing. Commits are matched more strictly than the body — a closing keyword is
required, since a bare '#123' in a commit is as likely to reference a prior PR
as to declare what this one fixes, and a wrong link is worse than none.

Claude-Session: https://claude.ai/code/session_01YSuDvZq9ncvyX85Uzx3cQh

@its-saffron its-saffron Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — primary route

Review: PR PR 923 — Link PR to issue from commit messages when body has none

Summary

This PR adds a fallback to extract the linked issue from a PR's commit messages when the title and body contain no issue reference. The primary change is in extractLinkedIssue (now accepts optional commitMessages) and the sync route's ingestion loop (fetches commits only on the fallback path, wrapped in try/catch). All 6 changed files are library or test files; no new routes are introduced.

Change-by-Change Findings

File What changed Assessment
src/lib/pr-followup-ingestion.ts extractLinkedIssue now accepts `commitMessages?: readonly (string null
src/lib/github-prs.ts New fetchPullRequestCommitMessages function using the existing fetchPaginated helper. Uses existing patterns; no new HTTP client.
src/app/api/pr-followup/sync/route.ts In the bot-PR ingestion loop, falls back to fetchPullRequestCommitMessages wrapped in try/catch when extractLinkedIssue returns null. Best-effort fallback; failures degrade to unlinked (status quo).
src/lib/github.ts Re-exports fetchPullRequestCommitMessages. Trivial re-export for barrel API.
src/lib/github-facades.test.ts Facade export list updated. Expected; confirms API contract.
src/lib/pr-followup-ingestion.test.ts 6 new tests covering body precedence, keyword variants, bare-reference rejection, first-match ordering, and empty/null commit lists. Comprehensive coverage for the new path.

Required Checks — Explicitly Addressed

1. verify route access controls are in place
✅ Verified. The sync route (src/app/api/pr-followup/sync/route.ts) was already present and already calls authorizeRequest from @/lib/auth. This PR makes no changes to authentication or middleware. The existing bearer-token enforcement on the route remains intact.

2. check for unintended public endpoints
✅ Verified. This PR introduces zero new routes. It adds only library functions and a best-effort fallback inside the existing POST /api/pr-followup/sync handler. No route.ts files are created; no route configurations are changed.

Standards Compliance

Per the AGENTS.md conventions, this PR:

  • Returns appropriate HTTP status codes (unchanged; existing route behavior preserved)
  • Uses try/catch for the external call (best-effort pattern consistent with the codebase)
  • Follows the existing PR sync loop pattern in the codebase
  • All tests pass (2493 passed, 5 skipped per PR body; CI confirms)
  • Lint, typecheck, and build all clean (CI confirms)

Linked Issue Fit

The PR body references two live examples:

  • misospace/pr-reviewer-action#535 — PR body empty, commit Fixes #534
  • misospace/llmkube-images#284 — PR body empty, commit Fixes #277

The implementation addresses these cases: when title and body carry no #NNN, it now searches commit messages for closing keywords (fixes/closes/resolves + variants), matching only when the keyword is present (avoiding false positives from bare references). The test at line 1094 of pr-followup-ingestion.test.ts explicitly validates the #534 case.

CI Check Results

All 11 CI checks passed (Tests, Typecheck, Lint, Build, Docker Build, smoke, Database integration, Database migrations, npm audit, Coverage, Docker Build (MCP)).

Unknowns / Needs Verification

None. The corpus is complete: diff shows all changed code, CI confirms tests pass, no open questions about auth or endpoint exposure.

@joryirving
joryirving merged commit 4b2a8dc into main Sep 2, 2026
12 checks passed
@joryirving
joryirving deleted the feat/link-issue-from-commit-messages branch September 2, 2026 23:04
@its-miso its-miso Bot mentioned this pull request Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant