Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit6ea2109unknown_key

perf(server): bound issues query + git-diff timeouts + parallel spawns

perf(server): bound issues query + git-diff timeouts + parallel spawns

Three runtime bottlenecks flagged by the bottleneck audit:

1. issues.tsx:624 — issue list was unbounded. With 10k+ issues per
   repo it ran a full table scan + O(n) sort on every page load.
   Add ?page + ?per_page (default 50, max 100) + .limit/.offset.

2. pulls.tsx:1206 — AI PR summary git diff had no timeout. A
   pathological diff (huge binary, corrupt ref) would hang the
   request thread indefinitely. Wrap in a 30s setTimeout that
   proc.kill()s the subprocess.

3. pulls.tsx:1413 — PR detail "Files changed" tab ran two git diffs
   sequentially (text + numstat). On 100+-file PRs that's ~10-30s
   for no reason. Parallelise with Promise.all + add the same 30s
   timeout to both spawns.
Claude committed on May 24, 2026Parent: f077ea5
2 files changed+3686ea2109a0a0134475c5b08d925408f6efb8ee7cc
2 changed files+36−8
Modifiedsrc/routes/issues.tsx+8−1View fileUnifiedSplit
608608 const { owner: ownerName, repo: repoName } = c.req.param();
609609 const user = c.get("user");
610610 const state = c.req.query("state") || "open";
611 // Bounded pagination — unbounded selects ran a full table scan + O(n)
612 // sort on every page load; with 10k+ issues the request would hang.
613 const perPage = Math.min(100, Math.max(1, Number(c.req.query("per_page")) || 50));
614 const page = Math.max(1, Number(c.req.query("page")) || 1);
615 const offset = (page - 1) * perPage;
611616
612617 const resolved = await resolveRepo(ownerName, repoName);
613618 if (!resolved) {
631636 .where(
632637 and(eq(issues.repositoryId, repo.id), eq(issues.state, state))
633638 )
634 .orderBy(desc(issues.createdAt));
639 .orderBy(desc(issues.createdAt))
640 .limit(perPage)
641 .offset(offset);
635642
636643 // Count open/closed
637644 const [counts] = await db
Modifiedsrc/routes/pulls.tsx+28−7View fileUnifiedSplit
12121212 ],
12131213 { cwd, stdout: "pipe", stderr: "pipe" }
12141214 );
1215 diff = await new Response(proc.stdout).text();
1216 await proc.exited;
1215 // 30s ceiling — without this a pathological diff (huge binary or
1216 // a corrupt ref) hangs the request indefinitely.
1217 const killer = setTimeout(() => proc.kill(), 30_000);
1218 try {
1219 diff = await new Response(proc.stdout).text();
1220 await proc.exited;
1221 } finally {
1222 clearTimeout(killer);
1223 }
12171224 } catch {
12181225 diff = "";
12191226 }
14101417 let diffFiles: GitDiffFile[] = [];
14111418 if (tab === "files") {
14121419 const repoDir = getRepoPath(ownerName, repoName);
1420 // Run the two git diffs in parallel — they're independent reads of
1421 // the same range. Previously sequential, doubling the wall time on
1422 // big PRs (100+ files = 10-30s for no reason).
14131423 const proc = Bun.spawn(
14141424 ["git", "diff", `${pr.baseBranch}...${pr.headBranch}`],
14151425 { cwd: repoDir, stdout: "pipe", stderr: "pipe" }
14161426 );
1417 diffRaw = await new Response(proc.stdout).text();
1418 await proc.exited;
1419
14201427 const statProc = Bun.spawn(
14211428 ["git", "diff", "--numstat", `${pr.baseBranch}...${pr.headBranch}`],
14221429 { cwd: repoDir, stdout: "pipe", stderr: "pipe" }
14231430 );
1424 const stat = await new Response(statProc.stdout).text();
1425 await statProc.exited;
1431 // 30s ceiling per spawn — a corrupt ref / pathological binary diff
1432 // would otherwise hang the whole request.
1433 const killer = setTimeout(() => {
1434 proc.kill();
1435 statProc.kill();
1436 }, 30_000);
1437 let stat = "";
1438 try {
1439 [diffRaw, stat] = await Promise.all([
1440 new Response(proc.stdout).text(),
1441 new Response(statProc.stdout).text(),
1442 ]);
1443 await Promise.all([proc.exited, statProc.exited]);
1444 } finally {
1445 clearTimeout(killer);
1446 }
14261447
14271448 diffFiles = stat
14281449 .trim()
14291450