Skip to content

fix(api): wrap Slack JSON.parse calls in try-catch - #4144

Closed
vaishcodescape wants to merge 3 commits into
ParadevAI:mainfrom
vaishcodescape:fix/slack-json-parse-try-catch
Closed

vaishcodescape wants to merge 3 commits into
ParadevAI:mainfrom
vaishcodescape:fix/slack-json-parse-try-catch

Conversation

@vaishcodescape

@vaishcodescape vaishcodescape commented May 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Wraps JSON.parse() in try-catch in the Slack events (events/route.ts) and interactions (interactions/route.ts) API route handlers
  • Returns 400 Bad Request with { error: "Invalid JSON payload" } on malformed JSON instead of crashing with an unhandled exception (500)
  • Aligns with the existing pattern in link/route.ts which already handles parse errors gracefully

Problem

The Slack events and interactions endpoints call JSON.parse() without error handling. If the request body contains invalid JSON (e.g., corrupted in transit or truncated), the unhandled exception causes a 500 response. Slack then retries the event up to 3 times, each retry also failing — amplifying the issue.

Affected endpoints

Endpoint Events handled
slack/events app_mention, DM messages, link_shared, entity_details_requested, app_home_opened
slack/interactions model selection, account disconnect

Test plan

  • Verify Slack events (mentions, DMs) still work end-to-end
  • Verify Slack interactions (model select, disconnect) still work
  • Confirm malformed JSON payloads return 400 instead of 500

Summary by cubic

Batch-fetches GitHub PR statuses to speed up the desktop sidebar and cut N+1 GitHub calls. Also hardens Slack webhooks to return 400 on bad JSON instead of 500.

  • New Features

    • Batch PR status fetch for all worktree workspaces via a single GitHub GraphQL call.
    • Adds batchGetGitHubStatuses, batchFetchGitHubPRStatuses, and useBatchGitHubStatus to seed per-workspace caches without overwriting richer data.
    • Polls every 15s; updates query policy (diffs view focus-only, review tab polls) and removes hover gating for sidebar/list rows to keep status fresh.
  • Bug Fixes

    • Wraps JSON.parse in Slack events and interactions routes with try/catch.
    • Returns 400 with { error: "Invalid JSON payload" } on malformed bodies to prevent 500s and Slack retries.
    • Aligns with existing handling in link/route.ts.

Written for commit 26960da. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Batch GitHub PR status fetching for improved performance across workspaces.
    • Refined GitHub status polling behavior based on interface context for optimal responsiveness.
  • Bug Fixes

    • Enhanced error handling for invalid Slack event and interaction payloads.
  • Tests

    • Added comprehensive test coverage for batch PR status functionality, including GraphQL data normalization and edge cases.

- Introduced `batchGetGitHubStatuses` procedure to efficiently retrieve statuses for multiple workspaces.
- Enhanced GitHub utilities with `batchFetchGitHubPRStatuses` for improved performance.
- Updated workspace-related components to utilize the new batch fetching mechanism.
- Adjusted query policies to align with the new batch polling interval for workspace list items.
- Refactored related types and functions to support the new batch processing capabilities.
… handling

Unhandled JSON.parse exceptions in the Slack events and interactions
endpoints caused 500 responses on malformed payloads, triggering
Slack's retry loop. Now returns 400 consistently, matching the
existing pattern in link/route.ts.
@coderabbitai

coderabbitai Bot commented May 6, 2026 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 449947ae-5f64-4aa2-bfc1-aae366b2a032

📥 Commits

Reviewing files that changed from the base of the PR and between 442c13e and 26960da.

📒 Files selected for processing (15)
  • apps/api/src/app/api/integrations/slack/events/route.ts
  • apps/api/src/app/api/integrations/slack/interactions/route.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.test.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/index.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/pr-resolution.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/types.ts
  • apps/desktop/src/renderer/hooks/useBatchGitHubStatus.ts
  • apps/desktop/src/renderer/lib/githubQueryPolicy/githubQueryPolicy.test.ts
  • apps/desktop/src/renderer/lib/githubQueryPolicy/githubQueryPolicy.ts
  • apps/desktop/src/renderer/lib/githubQueryPolicy/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx

📝 Walkthrough

Walkthrough

This PR introduces two independent changes: hardening Slack API route handlers with guarded JSON parsing to return 400 errors on invalid payloads, and implementing a new batch GitHub PR status fetching system for desktop workspaces that groups queries by repository, executes a single GraphQL call, and integrates results via a React hook and updated query policies.

Changes

Slack JSON Payload Validation

Layer / File(s) Summary
Route Handler Error Handling
apps/api/src/app/api/integrations/slack/events/route.ts, apps/api/src/app/api/integrations/slack/interactions/route.ts
Both routes now wrap JSON.parse in try/catch blocks, declaring a typed payload variable and returning a 400 error on invalid JSON instead of throwing. Existing event handling paths remain unchanged.

GitHub Batch PR Status System

Layer / File(s) Summary
GraphQL Schemas & Type Definitions
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/types.ts
Introduces GHGraphQLPRNodeSchema, GHGraphQLCheckRunSchema, GHGraphQLStatusContextSchema, and reviewer schemas. Exports GHGraphQLPRNode type and normalizeGraphQLPR function to convert GraphQL PR nodes into the existing GHPRResponse shape.
Batch Fetching Core Logic
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.ts
New module exports batchFetchGitHubPRStatuses function that groups workspaces by repository, builds a single aliased GraphQL query, executes via gh api graphql, deduplicates PR candidates per branch, and returns a Map from workspaceId to GitHubStatus with robust error handling and result caching.
PR Resolution Helpers
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/pr-resolution.ts
Exports sortPRCandidates and formatPRData functions (previously internal) for use by batch fetching logic.
Module Exports
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/index.ts
Re-exports batchFetchGitHubPRStatuses and BatchWorkspaceEntry from ./batch-pr-status module.
tRPC Router Integration
apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts
Adds new publicProcedure batchGetGitHubStatuses that queries all non-deleted workspaces, calls batchFetchGitHubPRStatuses, updates worktrees with new statuses, and returns a mapping from workspaceId to GitHubStatus or null.
Query Polling Constants
apps/desktop/src/renderer/lib/githubQueryPolicy/githubQueryPolicy.ts, apps/desktop/src/renderer/lib/githubQueryPolicy/index.ts
Replaces previous global GITHUB_STATUS constants with granular constants per-surface: ACTIVE_GITHUB_STATUS_STALE_TIME_MS, ACTIVE_GITHUB_STATUS_REFETCH_INTERVAL_MS, BATCH_GITHUB_STATUS_REFETCH_INTERVAL_MS (exported), WORKSPACE_LIST_ITEM_GITHUB_STATUS_STALE_TIME_MS, and PASSIVE_GITHUB_STATUS_STALE_TIME_MS.
Query Policy Tests
apps/desktop/src/renderer/lib/githubQueryPolicy/githubQueryPolicy.test.ts
Replaces broad tests with per-surface fine-grained test cases covering changes-sidebar (active/inactive, with/without review mode), workspace-page, workspace-hover-card, workspace-list-item, and workspace-row, with specific expectations for polling intervals, focus-refresh behavior, and staleTime.
React Hook
apps/desktop/src/renderer/hooks/useBatchGitHubStatus.ts
New hook useBatchGitHubStatus that calls electronTrpc.workspaces.batchGetGitHubStatuses.useQuery and seeds per-workspace getGitHubStatus caches without overwriting existing richer data.
Component Integration
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
Replaces V2AvailableBanner import with useBatchGitHubStatus and calls the hook to trigger batch GitHub status updates.
Per-Workspace Status Queries
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx, apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
Both components now define GitHub status query policies with surface names and call electronTrpc.workspaces.getGitHubStatus.useQuery to fetch per-workspace status data for display. WorkspaceRow removes the openFileInEditor mutation.
Tests
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.test.ts
New test file validates GHGraphQLPRNodeSchema parsing (full node, minimal node, rejection on missing __typename) and normalizeGraphQLPR behavior (field mapping, statusCheckRollup normalization, null commits, reviewRequests, edge cases like drafts and merged state).

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related Issues

Possibly Related PRs

  • superset-sh/superset#233: Implements per-workspace GitHub PR status fetching; this PR extends it with batch fetching and query policy refinements.
  • superset-sh/superset#3125: Modifies GitHub status polling and affected components (githubQueryPolicy, WorkspaceListItem/WorkspaceRow/WorkspaceSidebar).

Poem

🐰 A warren of workspaces needs status in haste,
So batch up those GitHub calls—no time to waste!
One query, one answer, all branches aligned,
And Slack's JSON safe, with error designed. ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 6, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR has two distinct scopes: the stated fix wraps JSON.parse() in try-catch for the Slack events and interactions routes to return 400 on malformed payloads instead of crashing, and a separate large desktop feature introduces batch GitHub PR status fetching via a single GraphQL query to replace N+1 per-workspace gh pr view calls.

  • Slack routes — try-catch blocks are correct, but the new let payload: Record<string, unknown> annotation causes downstream TypeScript errors (e.g. payload.team?.id, payload.actions, event.type) because indexed values become unknown instead of any; the existing code compiled with the implicit any from JSON.parse.
  • Batch GitHub status — new batchFetchGitHubPRStatuses function, batchGetGitHubStatuses TRPC procedure, and useBatchGitHubStatus hook are well-structured; escapeGraphQLString is missing newline/CR escaping; batch entries omit headSha, so PR matching is less precise than the per-workspace path.
  • Hover-gate removal — isActive is now always true for worktrees in both WorkspaceListItem and WorkspaceRow, but this causes all individual getGitHubStatus queries to fire on mount before the batch hook can seed the cache, preserving the N+1 problem on initial load.

Confidence Score: 3/5

The Slack route changes are safe at runtime but likely break the TypeScript build in strict mode; the desktop batch feature is additive but the hover-gate removal may not fully deliver the N+1 fix it intends to.

The Record<string, unknown> annotation in both Slack route files changes downstream property accesses from any (which TypeScript allows freely) to unknown (which TypeScript forbids without narrowing). Sites like payload.team?.id, iterating payload.actions, and reading event.type after destructuring would all be compiler errors in a strict-mode project — the same code compiled cleanly before because JSON.parse() returns any. If the CI pipeline runs tsc, this blocks the build. The batch desktop feature is architecturally sound but the hover-gate removal may cause all N workspace queries to fire simultaneously on initial mount before the batch result arrives.

Both Slack route files (events/route.ts and interactions/route.ts) need the type annotation corrected before merging. batch-pr-status.ts and git-status.ts are worth a second look for the headSha omission and escape function.

Important Files Changed

Filename Overview
apps/api/src/app/api/integrations/slack/events/route.ts Adds try-catch around JSON.parse. The Record<string, unknown> annotation makes all downstream accesses (event.type, event.channel_type, etc.) TypeScript errors in strict mode.
apps/api/src/app/api/integrations/slack/interactions/route.ts Adds try-catch around JSON.parse. Same type-annotation issue: payload.team?.id, payload.user?.id, payload.actions iteration are all TypeScript errors with Record<string, unknown>.
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.ts New batch GraphQL PR-status fetcher. Logic is mostly solid; escapeGraphQLString is incomplete (missing newline/CR); cwd is taken from entries[0] which may misapply GitHub auth when entries span multiple hosts.
apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts Adds batchGetGitHubStatuses procedure. Correct overall; headSha is not populated in batch entries, reducing PR-match precision compared to per-workspace path.
apps/desktop/src/renderer/hooks/useBatchGitHubStatus.ts New hook that polls batch status and seeds per-workspace cache only when empty. Seeding logic is safe; races with per-workspace queries on initial load.
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx isActive no longer gated on hover; enables getGitHubStatus for all worktrees immediately, causing N individual queries on initial mount before batch seeds cache.
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/types.ts Adds GraphQL PR node schemas and normalizeGraphQLPR converter. Schema design with discriminated unions is correct; normalisation of statusCheckRollup and reviewRequests is clean.
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.test.ts New test file covering schema parsing and normalizeGraphQLPR. Good coverage of edge cases including null nodes, draft PRs, cross-repo PRs, and GHPRResponseSchema compatibility.

Sequence Diagram

sequenceDiagram
    participant Slack
    participant EventsRoute as slack/events
    participant InteractionsRoute as slack/interactions
    participant QStash

    Slack->>EventsRoute: POST (raw body)
    EventsRoute->>EventsRoute: verifySlackSignature()
    EventsRoute->>EventsRoute: JSON.parse(body) [try-catch]
    alt malformed JSON
        EventsRoute-->>Slack: 400 Invalid JSON payload
    else valid JSON
        EventsRoute->>QStash: publishJSON (mention/DM job)
        EventsRoute-->>Slack: 200 ok
    end

    Slack->>InteractionsRoute: POST (form-encoded body)
    InteractionsRoute->>InteractionsRoute: verifySlackSignature()
    InteractionsRoute->>InteractionsRoute: JSON.parse(payloadRaw) [try-catch]
    alt malformed JSON
        InteractionsRoute-->>Slack: 400 Invalid JSON payload
    else valid JSON
        InteractionsRoute->>InteractionsRoute: handleModelSelect / handleDisconnectAccount
        InteractionsRoute-->>Slack: 200 ok
    end
Loading

Comments Outside Diff (2)

  1. apps/api/src/app/api/integrations/slack/interactions/route.ts, line 40-51 (link)

    P1 Record<string, unknown> annotation breaks downstream TypeScript

    Changing payload from implicit any (returned by JSON.parse) to let payload: Record<string, unknown> makes every indexed value typed as unknown. Accessing payload.team?.id, payload.user?.id, and iterating payload.actions are all TypeScript errors because you cannot call property-access or optional-chaining on unknown without first narrowing — the TypeScript compiler will reject Object is of type 'unknown' for each of these sites. The same issue exists in events/route.ts where event.type, event.channel_type, etc. are accessed after destructuring a Record<string, unknown>. Using as any cast on the assignment (payload = JSON.parse(payloadRaw) as any) or widening to Record<string, any> would preserve runtime behavior without breaking the type checker.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/api/src/app/api/integrations/slack/interactions/route.ts
    Line: 40-51
    
    Comment:
    **`Record<string, unknown>` annotation breaks downstream TypeScript**
    
    Changing `payload` from implicit `any` (returned by `JSON.parse`) to `let payload: Record<string, unknown>` makes every indexed value typed as `unknown`. Accessing `payload.team?.id`, `payload.user?.id`, and iterating `payload.actions` are all TypeScript errors because you cannot call property-access or optional-chaining on `unknown` without first narrowing — the TypeScript compiler will reject `Object is of type 'unknown'` for each of these sites. The same issue exists in `events/route.ts` where `event.type`, `event.channel_type`, etc. are accessed after destructuring a `Record<string, unknown>`. Using `as any` cast on the assignment (`payload = JSON.parse(payloadRaw) as any`) or widening to `Record<string, any>` would preserve runtime behavior without breaking the type checker.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx, line 148-151 (link)

    P2 N+1 individual queries still fire on initial mount

    Now that isActive is always true for worktrees (no longer gated on hasHovered), every WorkspaceListItem immediately enables its getGitHubStatus query. React Query fires all of these the moment the sidebar renders — before useBatchGitHubStatus in WorkspaceSidebar has resolved and seeded the cache. The batch approach avoids N+1 polls, but the initial load still spawns N individual gh pr view processes in parallel. Consider keeping isActive: false in this policy and relying exclusively on the batch hook to seed the per-workspace cache, then only enabling individual queries when the batch result is stale or missing for that workspace.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
    Line: 148-151
    
    Comment:
    **N+1 individual queries still fire on initial mount**
    
    Now that `isActive` is always `true` for worktrees (no longer gated on `hasHovered`), every `WorkspaceListItem` immediately enables its `getGitHubStatus` query. React Query fires all of these the moment the sidebar renders — before `useBatchGitHubStatus` in `WorkspaceSidebar` has resolved and seeded the cache. The batch approach avoids N+1 polls, but the initial load still spawns N individual `gh pr view` processes in parallel. Consider keeping `isActive: false` in this policy and relying exclusively on the batch hook to seed the per-workspace cache, then only enabling individual queries when the batch result is stale or missing for that workspace.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
apps/api/src/app/api/integrations/slack/interactions/route.ts:40-51
**`Record<string, unknown>` annotation breaks downstream TypeScript**

Changing `payload` from implicit `any` (returned by `JSON.parse`) to `let payload: Record<string, unknown>` makes every indexed value typed as `unknown`. Accessing `payload.team?.id`, `payload.user?.id`, and iterating `payload.actions` are all TypeScript errors because you cannot call property-access or optional-chaining on `unknown` without first narrowing — the TypeScript compiler will reject `Object is of type 'unknown'` for each of these sites. The same issue exists in `events/route.ts` where `event.type`, `event.channel_type`, etc. are accessed after destructuring a `Record<string, unknown>`. Using `as any` cast on the assignment (`payload = JSON.parse(payloadRaw) as any`) or widening to `Record<string, any>` would preserve runtime behavior without breaking the type checker.

### Issue 2 of 4
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.ts:317-319
**`escapeGraphQLString` does not escape control characters**

The function only replaces `\` and `"`, but GraphQL string literals forbid unescaped newline (`\n`), carriage-return (`\r`), and other control characters. While git refs and GitHub owner/repo names cannot contain newlines today, a more complete escape protects against unexpected inputs and matches GraphQL spec requirements.

```suggestion
function escapeGraphQLString(value: string): string {
	return value
		.replace(/\\/g, "\\\\")
		.replace(/"/g, '\\"')
		.replace(/\n/g, "\\n")
		.replace(/\r/g, "\\r")
		.replace(/\t/g, "\\t");
}
```

### Issue 3 of 4
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx:148-151
**N+1 individual queries still fire on initial mount**

Now that `isActive` is always `true` for worktrees (no longer gated on `hasHovered`), every `WorkspaceListItem` immediately enables its `getGitHubStatus` query. React Query fires all of these the moment the sidebar renders — before `useBatchGitHubStatus` in `WorkspaceSidebar` has resolved and seeded the cache. The batch approach avoids N+1 polls, but the initial load still spawns N individual `gh pr view` processes in parallel. Consider keeping `isActive: false` in this policy and relying exclusively on the batch hook to seed the per-workspace cache, then only enabling individual queries when the batch result is stale or missing for that workspace.

### Issue 4 of 4
apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts:223-235
**`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.

Reviews (1): Last reviewed commit: "Merge branch 'main' into fix/slack-json-..." | Re-trigger Greptile

Comment on lines +317 to +319
function escapeGraphQLString(value: string): string {
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}

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 escapeGraphQLString does not escape control characters

The function only replaces \ and ", but GraphQL string literals forbid unescaped newline (\n), carriage-return (\r), and other control characters. While git refs and GitHub owner/repo names cannot contain newlines today, a more complete escape protects against unexpected inputs and matches GraphQL spec requirements.

Suggested change
function escapeGraphQLString(value: string): string {
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
function escapeGraphQLString(value: string): string {
return value
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/\t/g, "\\t");
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/lib/trpc/routers/workspaces/utils/github/batch-pr-status.ts
Line: 317-319

Comment:
**`escapeGraphQLString` does not escape control characters**

The function only replaces `\` and `"`, but GraphQL string literals forbid unescaped newline (`\n`), carriage-return (`\r`), and other control characters. While git refs and GitHub owner/repo names cannot contain newlines today, a more complete escape protects against unexpected inputs and matches GraphQL spec requirements.

```suggestion
function escapeGraphQLString(value: string): string {
	return value
		.replace(/\\/g, "\\\\")
		.replace(/"/g, '\\"')
		.replace(/\n/g, "\\n")
		.replace(/\r/g, "\\r")
		.replace(/\t/g, "\\t");
}
```

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

Comment on lines +223 to +235

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;

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.

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