Skip to content
Closed
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
7 changes: 6 additions & 1 deletion apps/api/src/app/api/integrations/slack/events/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ export async function POST(request: Request) {
return Response.json({ error: "Invalid signature" }, { status: 401 });
}

const payload = JSON.parse(body);
let payload: Record<string, unknown>;
try {
payload = JSON.parse(body);
} catch {
return Response.json({ error: "Invalid JSON payload" }, { status: 400 });
}

// Slack sends this once when configuring the Events URL
if (payload.type === "url_verification") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ export async function POST(request: Request) {
return new Response("ok", { status: 200 });
}

const payload = JSON.parse(payloadRaw);
let payload: Record<string, unknown>;
try {
payload = JSON.parse(payloadRaw);
} catch {
return Response.json({ error: "Invalid JSON payload" }, { status: 400 });
}

if (payload.type === "block_actions") {
const teamId: string = payload.team?.id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
refreshDefaultBranch,
} from "../utils/git";
import {
clearGitHubCachesForWorktree,
batchFetchGitHubPRStatuses,
fetchGitHubPRComments,
fetchGitHubPRStatus,
type PullRequestCommentsTarget,
Expand Down Expand Up @@ -214,6 +214,68 @@ export const createGitStatusProcedures = () => {
return freshStatus;
}),

batchGetGitHubStatuses: publicProcedure.query(async () => {
const allWorkspaceRows = localDb
.select()
.from(workspaces)
.where(isNull(workspaces.deletingAt))
.all();

const allWorktreeRows = localDb.select().from(worktrees).all();
const worktreeMap = new Map(allWorktreeRows.map((wt) => [wt.id, wt]));
const workspaceMap = new Map(
allWorkspaceRows.map((ws) => [ws.id, ws]),
);

const entries = allWorkspaceRows
.filter((ws) => ws.type === "worktree" && ws.worktreeId)
.map((ws) => {
// biome-ignore lint/style/noNonNullAssertion: filtered above
const wt = worktreeMap.get(ws.worktreeId!);
if (!wt) return null;
Comment on lines +223 to +235

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.

P2 headSha is omitted from batch entries, reducing PR-match precision

BatchWorkspaceEntry.headSha is optional, but it is used by both shouldAcceptPRMatch and sortPRCandidates to prefer the PR whose headRefOid matches the local commit. Because the procedure never reads the worktree's current HEAD sha and never populates headSha, the batch path uses branch-name matching only — meaning it can silently pick a stale or incorrect PR when multiple PRs share the same branch name. The per-workspace getGitHubStatus path reads the sha, so the two code paths can disagree on which PR to display.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts
Line: 223-235

Comment:
**`headSha` is omitted from batch entries, reducing PR-match precision**

`BatchWorkspaceEntry.headSha` is optional, but it is used by both `shouldAcceptPRMatch` and `sortPRCandidates` to prefer the PR whose `headRefOid` matches the local commit. Because the procedure never reads the worktree's current HEAD sha and never populates `headSha`, the batch path uses branch-name matching only — meaning it can silently pick a stale or incorrect PR when multiple PRs share the same branch name. The per-workspace `getGitHubStatus` path reads the sha, so the two code paths can disagree on which PR to display.

How can I resolve this? If you propose a fix, please make it concise.

return {
workspaceId: ws.id,
worktreePath: wt.path,
branch: wt.branch,
};
})
.filter((e): e is NonNullable<typeof e> => e !== null);

const batchResults = await batchFetchGitHubPRStatuses(entries);

const result: Record<string, GitHubStatus | null> = {};

for (const [workspaceId, freshStatus] of batchResults) {
result[workspaceId] = freshStatus;

const ws = workspaceMap.get(workspaceId);
if (!ws?.worktreeId) continue;
const wt = worktreeMap.get(ws.worktreeId);
if (!wt) continue;

if (
hasMeaningfulGitHubStatusChange({
current: wt.githubStatus,
next: freshStatus,
})
) {
localDb
.update(worktrees)
.set({ githubStatus: freshStatus })
.where(eq(worktrees.id, wt.id))
.run();
}
}

for (const ws of allWorkspaceRows) {
if (!(ws.id in result)) {
result[ws.id] = null;
}
}

return result;
}),

getGitHubPRComments: publicProcedure
.input(gitHubPRCommentsInputSchema)
.query(async ({ input }) => {
Expand Down
Loading