Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions app/api/webhooks/github/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import {
generatePullRequestSummary,
reviewPullRequest,
} from "@/lib/ai/actions";
import { reviewPullRequest } from "@/lib/ai/actions";
import { inngest } from "@/lib/inngest";

import { Webhooks } from "@octokit/webhooks";
Expand Down Expand Up @@ -110,29 +107,17 @@ export async function POST(request: NextRequest) {
// Trigger AI review when PR is opened or updated
if (action === "opened" || action === "synchronized") {
try {
await generatePullRequestSummary(
await reviewPullRequest(
owner,
repoName,
prNumber,
pull_request?.title,
pull_request?.body,
body?.installation?.id,
pull_request?.base?.sha,
pull_request?.head?.sha,
pull_request?.changed_files,
pull_request?.additions,
pull_request?.deletions
);
Comment on lines +110 to 120
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the files mentioned in the review
git ls-files | grep -E "(webhook|route)" | head -20

Repository: afuhflynn/code-reverb

Length of output: 719


🏁 Script executed:

# Check the structure of the repository
git ls-files | grep -E "^app/|^lib/" | head -30

Repository: afuhflynn/code-reverb

Length of output: 938


🏁 Script executed:

# Read the webhook route file around the mentioned lines
cat app/api/webhooks/github/route.ts | head -150

Repository: afuhflynn/code-reverb

Length of output: 4512


🏁 Script executed:

# Check the lib/ai/actions file mentioned
cat -n lib/ai/actions/index.ts | head -50

Repository: afuhflynn/code-reverb

Length of output: 1608


🏁 Script executed:

# Look for the reviewPullRequest function to see its signature
grep -r "reviewPullRequest" --include="*.ts" | head -20

Repository: afuhflynn/code-reverb

Length of output: 284


🏁 Script executed:

# Check the actual TypeScript configuration and any type assertions
head -20 tsconfig.json 2>/dev/null || echo "No tsconfig.json in root"

Repository: afuhflynn/code-reverb

Length of output: 506


🏁 Script executed:

# Look for any error handling or guards around the reviewPullRequest call
sed -n '100,135p' app/api/webhooks/github/route.ts

Repository: afuhflynn/code-reverb

Length of output: 1139


🏁 Script executed:

# Check if reviewPullRequest has error handling for undefined values
sed -n '1,80p' lib/ai/actions/index.ts

Repository: afuhflynn/code-reverb

Length of output: 2026


🏁 Script executed:

# Verify GitHub webhook documentation specifics by checking if there are any comments or guards in the code
grep -A 10 "pull_request" app/api/webhooks/github/route.ts | head -30

Repository: afuhflynn/code-reverb

Length of output: 943


🏁 Script executed:

# Check if TypeScript compilation has errors with strict mode
# Look for any type assertions or non-null assertions in the code
grep -n "!" app/api/webhooks/github/route.ts | head -20

Repository: afuhflynn/code-reverb

Length of output: 190


🏁 Script executed:

# Check if there's a build/compilation output or error log
find . -name "*.log" -o -name ".next" -type d 2>/dev/null | head -10

Repository: afuhflynn/code-reverb

Length of output: 47


🏁 Script executed:

# Look for any type casting or validation before the function call
sed -n '95,125p' app/api/webhooks/github/route.ts

Repository: afuhflynn/code-reverb

Length of output: 994


Fix TypeScript type safety: parameters passed to reviewPullRequest may be undefined.

The function signature in lib/ai/actions/index.ts (lines 7-16) requires non-optional parameters (installationId: number, baseSha: string, etc.), but the webhook call passes potentially undefined values via optional chaining. While GitHub webhook payloads guarantee these fields exist for opened and synchronized pull_request events, TypeScript's strict mode will reject this mismatch.

Either add non-null assertions (!), validate before the call, or update the function signature to accept optional parameters with appropriate fallbacks.

🤖 Prompt for AI Agents
In app/api/webhooks/github/route.ts around lines 110 to 120, the call to
reviewPullRequest passes values that may be undefined (via optional chaining)
while the function signature in lib/ai/actions/index.ts requires non-optional
parameters; fix by either (A) asserting non-null for the guaranteed fields at
call site (use ! on installation id, base.sha, head.sha,
changed_files/additions/deletions) or (B) add runtime validation before calling
reviewPullRequest and return or throw a clear error if any required field is
missing, or (C) change the reviewPullRequest signature to accept optional
parameters and handle defaults inside the function—apply one consistent approach
and update types accordingly.

console.log(`Summary completed for: ${repo} #${prNumber}`);
} catch (error) {
console.error(
`Summarization failed for repo: ${repo} #${prNumber}: `,
error
);
}

try {
await reviewPullRequest(owner, repoName, prNumber);
console.log(`Review completed for: ${repo} #${prNumber}`);
} catch (error) {
console.error(
Expand Down
93 changes: 26 additions & 67 deletions lib/ai/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { prisma } from "@/lib/prisma";
export async function reviewPullRequest(
owner: string,
repo: string,
prNumber: number
prNumber: number,
installationId: number,
baseSha: string,
headSha: string,
changedFiles: number,
additions: number,
deletions: number
) {
Comment on lines 7 to 17
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Type safety issue: function signature doesn't match actual usage.

The function signature declares installationId, baseSha, headSha, changedFiles, additions, and deletions as required parameters with non-nullable types. However, the caller in app/api/webhooks/github/route.ts (lines 114-119) passes potentially undefined values using optional chaining (body?.installation?.id, pull_request?.base?.sha, etc.). Additionally, the null coalescing operators used in the event payload (lines 109, 112-114) suggest these values can indeed be null or undefined.

🔎 Proposed fix: Make parameters optional
 export async function reviewPullRequest(
   owner: string,
   repo: string,
   prNumber: number,
-  installationId: number,
-  baseSha: string,
-  headSha: string,
-  changedFiles: number,
-  additions: number,
-  deletions: number
+  installationId?: number,
+  baseSha?: string,
+  headSha?: string,
+  changedFiles?: number,
+  additions?: number,
+  deletions?: number
 ) {

try {
const repository = await prisma.repo.findFirst({
Expand Down Expand Up @@ -90,6 +96,25 @@ export async function reviewPullRequest(
},
});

await inngest.send({
name: "pr.summary.requested",
id: `review-${repository.id}-${prNumber}`,
data: {
owner,
repo,
prNumber,
title: title ?? "",
description: description ?? "",
accountId: githubAccount.accountId,
installationId: installationId ?? null,
baseSha,
headSha,
changedFiles: changedFiles ?? 0,
additions: additions ?? 0,
deletions: deletions ?? 0,
},
});
Comment on lines +99 to +116
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Inngest event ID deduplication idempotency behavior

💡 Result:

Summary — Inngest event-ID deduplication / idempotency

  • Producer-level dedupe: if you send an event with an explicit id, Inngest uses that id as an idempotency key and will ignore any subsequently received events with the same id for 24 hours (duplicates are stored in the event history but will not trigger functions). The id is global across event types, so make it unique to the event type + resource. [1]

  • Consumer-level idempotency: you can set a function-level idempotency CEL expression (idempotency key derived from event data). That key also prevents duplicate function runs for 24 hours (one unique expression → one execution per 24h). Useful when you can’t control producer ids or when different functions should dedupe differently. [2]

  • Important caveats: event-idempotency is ignored for debouncing, event batching, and while a function is paused (replays after unpausing may “skip” idempotency). Choose ids/expressions that are specific enough to avoid collisions across event types/functions. [2]

Sources: Inngest docs — Sending events / Deduplication; Handling idempotency (function-level). [1][2]


🏁 Script executed:

# Search for pr.review.requested event and its ID in the codebase
rg "pr\.review\.requested" -A 5 -B 5

Repository: afuhflynn/code-reverb

Length of output: 1165


Event ID collision: both events use the same ID causing deduplication.

Both the pr.summary.requested event (line 101) and the pr.review.requested event use identical IDs: review-${repository.id}-${prNumber}. Inngest treats event IDs as global idempotency keys across all event types and will silently drop any duplicate events with the same ID for 24 hours. This means one of these events will never be processed.

Use distinct event IDs for each event type:

     await inngest.send({
       name: "pr.summary.requested",
-      id: `review-${repository.id}-${prNumber}`,
+      id: `summary-${repository.id}-${prNumber}`,
       data: {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await inngest.send({
name: "pr.summary.requested",
id: `review-${repository.id}-${prNumber}`,
data: {
owner,
repo,
prNumber,
title: title ?? "",
description: description ?? "",
accountId: githubAccount.accountId,
installationId: installationId ?? null,
baseSha,
headSha,
changedFiles: changedFiles ?? 0,
additions: additions ?? 0,
deletions: deletions ?? 0,
},
});
await inngest.send({
name: "pr.summary.requested",
id: `summary-${repository.id}-${prNumber}`,
data: {
owner,
repo,
prNumber,
title: title ?? "",
description: description ?? "",
accountId: githubAccount.accountId,
installationId: installationId ?? null,
baseSha,
headSha,
changedFiles: changedFiles ?? 0,
additions: additions ?? 0,
deletions: deletions ?? 0,
},
});
🤖 Prompt for AI Agents
In lib/ai/actions/index.ts around lines 99 to 116, the event ID uses a generic
key `review-${repository.id}-${prNumber}` which collides with another event and
causes Inngest to dedupe one of them; change the sent event ID to include the
event type (for example append or prepend a unique token like `summary` or the
event name) so each event has a distinct idempotency key (e.g.,
`pr-summary-${repository.id}-${prNumber}`) and ensure the other event uses a
different type-specific ID as well.


await inngest.send({
name: "pr.review.requested",
id: `review-${repository.id}-${prNumber}`,
Expand Down Expand Up @@ -136,69 +161,3 @@ export async function reviewPullRequest(
}
}
}

export async function generatePullRequestSummary(
owner: string,
repoName: string,
prNumber: number,
title: string,
description: string,
installationId: number,
baseSha: string,
headSha: string,
changedFiles: number,
additions: number,
deletions: number
) {
try {
const repository = await prisma.repo.findFirst({
where: {
name: repoName,
fullName: `${owner}/${repoName}`,
},
include: {
owner: {
include: {
accounts: {
where: {
providerId: "github",
},
},
},
},
},
});

if (!repository)
throw new Error(
`Repository ${owner}/${repoName} not found in databse. Please reconnect the repository.`
);

const githubAccount = repository.owner.accounts[0];
await inngest.send({
name: "pr.summary.requested",
id: `review-${repository.id}-${prNumber}`,
data: {
owner,
repo: repoName,
prNumber,
title: title ?? "",
description: description ?? "",
accountId: githubAccount.accountId,
installationId: installationId ?? null,
baseSha,
headSha,
changedFiles: changedFiles ?? 0,
additions: additions ?? 0,
deletions: deletions ?? 0,
},
});

return {
sucess: true,
message: "PR Summary Queued",
};
} catch (error) {
console.error("Failed to post PR Summary ", error);
}
}