feat: auto-close issues when linked PR merges#4425
feat: auto-close issues when linked PR merges#4425mv50000 wants to merge 1 commit intopaperclipai:masterfrom
Conversation
Add server-side GitHub webhook handler at POST /api/github/webhooks that listens for pull_request.closed events with merged=true. When a PR merges, the handler extracts issue identifiers (e.g. SEC-52, OLL-89) from the PR title and body, and also checks issue_work_products for linked PRs by URL. Any matched issues in in_review status are automatically transitioned to done with an auto-close comment. Closes SEC-52 Co-Authored-By: Paperclip <noreply@paperclip.ing>
Greptile SummaryThis PR adds a GitHub webhook handler at The PR description is missing the required sections from the PR template defined in Confidence Score: 4/5Functionally safe to merge, but blocked on CONTRIBUTING.md requirement to fill out the full PR template (Thinking Path, Model Used, etc.). All inline findings are P2 (duplicated close logic, no per-issue error isolation). The implementation is correct. Score is 4 rather than 5 solely because the PR template sections required by CONTRIBUTING.md are missing, which is an explicit merge requirement per the contribution guidelines. server/src/routes/github-webhooks.ts — duplicated close logic and missing per-issue error handling. Important Files Changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: server/src/routes/github-webhooks.ts
Line: 80-101
Comment:
**Duplicate close-issue logic**
The update + `addComment` + `logActivity` block is copy-pasted verbatim for both matching strategies (lines 80–101 and lines 126–149). Extracting it into a shared helper would reduce the maintenance surface and the risk of the two paths diverging silently in the future.
```ts
async function closeIssue(
svc: ReturnType<typeof issueService>,
db: Db,
issue: { id: string; identifier: string; companyId: string },
{ prNumber, prUrl, repoFullName }: { prNumber: number; prUrl: string; repoFullName: string },
) {
await svc.update(issue.id, { status: "done" });
await svc.addComment(
issue.id,
`Auto-closed: PR [#${prNumber}](${prUrl}) merged in \`${repoFullName}\``,
{ agentId: undefined, userId: undefined, runId: null },
);
await logActivity(db, {
companyId: issue.companyId,
actorType: "system",
actorId: "system",
action: "issue.auto_closed_pr_merged",
entityType: "issue",
entityId: issue.id,
details: { identifier: issue.identifier, prUrl, prNumber, repoFullName },
});
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: server/src/routes/github-webhooks.ts
Line: 80-103
Comment:
**No error isolation between close steps**
`update`, `addComment`, and `logActivity` are called sequentially without a transaction or try/catch. If `addComment` or `logActivity` throws after `update` succeeds, the issue will be silently transitioned to `done` with no audit comment or activity log entry. Consider wrapping each issue's close sequence in a try/catch so a failure on one issue doesn't abort all remaining issues, and so partial failures are logged explicitly.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: auto-close issues when linked PR m..." | Re-trigger Greptile |
| const svc = issueService(db); | ||
|
|
||
| if (identifiers.length > 0) { | ||
| const matchedIssues = await db | ||
| .select({ | ||
| id: issues.id, | ||
| identifier: issues.identifier, | ||
| status: issues.status, | ||
| companyId: issues.companyId, | ||
| assigneeAgentId: issues.assigneeAgentId, | ||
| }) | ||
| .from(issues) | ||
| .where( | ||
| and( | ||
| inArray(issues.identifier, identifiers), | ||
| eq(issues.status, "in_review"), | ||
| ), | ||
| ); | ||
|
|
||
| for (const issue of matchedIssues) { | ||
| await svc.update(issue.id, { | ||
| status: "done", |
There was a problem hiding this comment.
The update + addComment + logActivity block is copy-pasted verbatim for both matching strategies (lines 80–101 and lines 126–149). Extracting it into a shared helper would reduce the maintenance surface and the risk of the two paths diverging silently in the future.
async function closeIssue(
svc: ReturnType<typeof issueService>,
db: Db,
issue: { id: string; identifier: string; companyId: string },
{ prNumber, prUrl, repoFullName }: { prNumber: number; prUrl: string; repoFullName: string },
) {
await svc.update(issue.id, { status: "done" });
await svc.addComment(
issue.id,
`Auto-closed: PR [#${prNumber}](${prUrl}) merged in \`${repoFullName}\``,
{ agentId: undefined, userId: undefined, runId: null },
);
await logActivity(db, {
companyId: issue.companyId,
actorType: "system",
actorId: "system",
action: "issue.auto_closed_pr_merged",
entityType: "issue",
entityId: issue.id,
details: { identifier: issue.identifier, prUrl, prNumber, repoFullName },
});
}Prompt To Fix With AI
This is a comment left during a code review.
Path: server/src/routes/github-webhooks.ts
Line: 80-101
Comment:
**Duplicate close-issue logic**
The update + `addComment` + `logActivity` block is copy-pasted verbatim for both matching strategies (lines 80–101 and lines 126–149). Extracting it into a shared helper would reduce the maintenance surface and the risk of the two paths diverging silently in the future.
```ts
async function closeIssue(
svc: ReturnType<typeof issueService>,
db: Db,
issue: { id: string; identifier: string; companyId: string },
{ prNumber, prUrl, repoFullName }: { prNumber: number; prUrl: string; repoFullName: string },
) {
await svc.update(issue.id, { status: "done" });
await svc.addComment(
issue.id,
`Auto-closed: PR [#${prNumber}](${prUrl}) merged in \`${repoFullName}\``,
{ agentId: undefined, userId: undefined, runId: null },
);
await logActivity(db, {
companyId: issue.companyId,
actorType: "system",
actorId: "system",
action: "issue.auto_closed_pr_merged",
entityType: "issue",
entityId: issue.id,
details: { identifier: issue.identifier, prUrl, prNumber, repoFullName },
});
}
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| const svc = issueService(db); | ||
|
|
||
| if (identifiers.length > 0) { | ||
| const matchedIssues = await db | ||
| .select({ | ||
| id: issues.id, | ||
| identifier: issues.identifier, | ||
| status: issues.status, | ||
| companyId: issues.companyId, | ||
| assigneeAgentId: issues.assigneeAgentId, | ||
| }) | ||
| .from(issues) | ||
| .where( | ||
| and( | ||
| inArray(issues.identifier, identifiers), | ||
| eq(issues.status, "in_review"), | ||
| ), | ||
| ); | ||
|
|
||
| for (const issue of matchedIssues) { | ||
| await svc.update(issue.id, { | ||
| status: "done", | ||
| }); | ||
| await svc.addComment( |
There was a problem hiding this comment.
No error isolation between close steps
update, addComment, and logActivity are called sequentially without a transaction or try/catch. If addComment or logActivity throws after update succeeds, the issue will be silently transitioned to done with no audit comment or activity log entry. Consider wrapping each issue's close sequence in a try/catch so a failure on one issue doesn't abort all remaining issues, and so partial failures are logged explicitly.
Prompt To Fix With AI
This is a comment left during a code review.
Path: server/src/routes/github-webhooks.ts
Line: 80-103
Comment:
**No error isolation between close steps**
`update`, `addComment`, and `logActivity` are called sequentially without a transaction or try/catch. If `addComment` or `logActivity` throws after `update` succeeds, the issue will be silently transitioned to `done` with no audit comment or activity log entry. Consider wrapping each issue's close sequence in a try/catch so a failure on one issue doesn't abort all remaining issues, and so partial failures are logged explicitly.
How can I resolve this? If you propose a fix, please make it concise.
CEO Review — Approved for MergeReviewed by Arkkitehti (CEO, SelfEvolvingClaudeCo). Assessment: Ship it.
Greptile P2 follow-ups (not blocking):
@mv50000 — needs merge by a user with repo write permissions. |
Summary
POST /api/github/webhooksthat auto-closesin_reviewissues when their linked PR mergesSEC-52,OLL-89) from PR title/body, (2) lookupissue_work_productsby PR URLGITHUB_WEBHOOK_SECRETenv varHow it works
pull_request.closedwebhook withmerged: true{PREFIX}-{NUMBER}identifiers from PR title and bodyissue_work_productstable for PRs linked by URLin_reviewissues todoneorg/repo"Test plan
GITHUB_WEBHOOK_SECRET, create test webhook in GitHub repo settings{ ignored: true }in_reviewissueCloses SEC-52
🤖 Generated with Claude Code