Repository navigation
feat(desktop): reply to PR review threads from the Changes pane - #7166
Conversation
The workspace Changes pane showed review threads with a resolve button but no way to answer them; only the PR view's Code tab could post replies. Bring the same flow over: - git.replyToReviewThread: workspace-scoped mutation that posts through GitHub's reply-to-review-comment endpoint, resolving the repo and PR number from the workspace's linked PR row. - The GitHub call and its error mapping move into a shared helper that pullRequests.replyToThread now uses too, so the two surfaces can't drift. - DiffPane's CommentThread gets a reply textarea (Cmd/Ctrl+Enter submits) and a Reply button beside Resolve; the thread annotation metadata carries the first comment's databaseId to thread onto. A rejected reply hands the draft back instead of losing it. All strings already exist in every locale from the PR view, so no catalog changes. Claude-Session: https://claude.ai/code/session_01DQF36hf8kmYHRggDxFwXuk
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (6)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe change adds a shared reply composer, propagates review comment IDs through diff annotations, adds a protected tRPC reply mutation, and centralizes GitHub review-comment reply handling. ChangesReview thread replies
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: ⚪ Minimal · up to This adds GitHub review-thread replies in both desktop thread views while preserving drafts on failures and refreshing threads after successful replies. No concrete current-head merge-blocking risk remains. Sequence Diagram(s)sequenceDiagram
participant DiffAnnotations
participant DiffPane
participant CommentThread
participant replyToReviewThread
participant replyToReviewComment
participant GitHub
DiffAnnotations->>DiffPane: provide replyToCommentId
DiffPane->>CommentThread: render reply composer
CommentThread->>replyToReviewThread: submit trimmed body
replyToReviewThread->>replyToReviewComment: pass PR and comment data
replyToReviewComment->>GitHub: create threaded review reply
GitHub-->>replyToReviewComment: return created comment id
replyToReviewThread-->>CommentThread: return mutation result
CommentThread->>CommentThread: clear draft and refresh thread list
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThe PR adds review-thread replies to the desktop workspace Changes pane and centralizes the GitHub reply operation shared with the pull-request Code tab. The UI derives a reply target from the first review comment, submits through a new workspace-scoped mutation, restores failed drafts, and refreshes thread annotations after success.
Confidence Score: 3/5The PR is not safe to merge until the new mutation enforces workspace-level access before posting to GitHub. Members of a shared host are authorized only at host granularity, and the new procedure accepts an arbitrary workspace ID without checking ownership, allowing an unauthorized reply to another user's linked pull request. Files Needing Attention: packages/host-service/src/trpc/router/git/git.ts
|
| Filename | Overview |
|---|---|
| packages/host-service/src/trpc/router/git/git.ts | Adds the workspace-scoped reply mutation, but its ID-only workspace lookup permits cross-workspace replies from members of a shared host. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/components/CommentThread/CommentThread.tsx | Adds a reply composer with keyboard submission, pending-state handling, query invalidation, and failed-draft restoration. |
| apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/hooks/useDiffAnnotations/useDiffAnnotations.ts | Propagates the first review comment's REST database ID as the thread reply target. |
| packages/host-service/src/trpc/router/git/utils/reply-to-review-comment.ts | Extracts the Octokit review-reply operation and existing error mapping into a behaviorally compatible shared helper. |
| packages/host-service/src/trpc/router/pull-requests/procedures/reply-to-thread.ts | Refactors the existing pull-request reply procedure onto the shared helper without changing its request or response contract. |
Sequence Diagram
sequenceDiagram
participant User as Shared-host member
participant Relay
participant Host as Host-service tRPC
participant DB as Local workspace DB
participant GitHub
User->>Relay: git.replyToReviewThread(workspaceId, commentId, body)
Relay->>Relay: Check organization and host membership
Relay->>Host: Forward unchanged tRPC input
Host->>DB: Find workspace by supplied ID
DB-->>Host: Linked pull-request row
Note over Host: Missing caller-to-workspace authorization
Host->>GitHub: createReplyForReviewComment(...)
GitHub-->>Host: New review comment
Prompt To Fix All With AI
### Issue 1
packages/host-service/src/trpc/router/git/git.ts:1069-1071
**Missing Workspace Authorization**
An authenticated member of a shared host can query another user's workspace threads and submit that workspace ID and one of its comment IDs here. Relay authorization checks host membership, but this lookup does not compare `workspace.createdByUserId` with `ctx.userId`. The mutation can therefore use the host's GitHub client to post a reply on another user's linked pull request. Enforce workspace-level access before resolving the pull request or posting the reply.
**How this was verified:** The relay forwards arbitrary tRPC input after checking only organization and host membership, and this procedure resolves the supplied workspace ID without a user-level authorization check before posting the reply.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(desktop): reply to PR review thread..." | Re-trigger Greptile
| }), | ||
| ) | ||
| .mutation(async ({ ctx, input }) => { |
There was a problem hiding this comment.
Missing Workspace Authorization
An authenticated member of a shared host can query another user's workspace threads and submit that workspace ID and one of its comment IDs here. Relay authorization checks host membership, but this lookup does not compare workspace.createdByUserId with ctx.userId. The mutation can therefore use the host's GitHub client to post a reply on another user's linked pull request. Enforce workspace-level access before resolving the pull request or posting the reply.
How this was verified: The relay forwards arbitrary tRPC input after checking only organization and host membership, and this procedure resolves the supplied workspace ID without a user-level authorization check before posting the reply.
Knowledge Base Used: Host service API and transport
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/git/git.ts
Line: 1069-1071
Comment:
**Missing Workspace Authorization**
An authenticated member of a shared host can query another user's workspace threads and submit that workspace ID and one of its comment IDs here. Relay authorization checks host membership, but this lookup does not compare `workspace.createdByUserId` with `ctx.userId`. The mutation can therefore use the host's GitHub client to post a reply on another user's linked pull request. Enforce workspace-level access before resolving the pull request or posting the reply.
**How this was verified:** The relay forwards arbitrary tRPC input after checking only organization and host membership, and this procedure resolves the supplied workspace ID without a user-level authorization check before posting the reply.
**Knowledge Base Used:** [Host service API and transport](https://app.greptile.com/personal-org-6810/-/custom-context/knowledge-base/superset-sh/superset/-/docs/host-service-api-and-transport.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
1 issue found across 8 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/host-service/src/trpc/router/git/git.ts">
<violation number="1" location="packages/host-service/src/trpc/router/git/git.ts:1072">
P0: Authorize `input.workspaceId` against `ctx.userId` before resolving its pull request. As written, any authenticated host member can supply another user's workspace ID and post a review reply through that workspace's linked pull request.</violation>
</file>
Architecture diagram
sequenceDiagram
participant UI as Changes Pane (DiffPane)
participant CT as CommentThread Component
participant TRPC as tRPC Router (Desktop Client)
participant HS as Host Service (gitRouter)
participant DB as Database
participant GH as GitHub API
Note over UI,GH: Reply to PR Review Thread from Changes Pane
UI->>UI: useDiffAnnotationsByPath()
UI->>UI: Extract first comment databaseId as replyToCommentId
UI->>CT: Pass replyToCommentId + workspaceId
CT->>CT: Initialize reply draft state
User->>CT: Type reply text
User->>CT: Click Reply button or Cmd/Ctrl+Enter
alt Blank or whitespace draft
CT-->>CT: Disable Reply button
else Draft has content and not in-flight
CT->>TRPC: git.replyToReviewThread.mutate({workspaceId, commentId, body})
CT->>CT: Clear draft textarea
end
alt replyToCommentId is undefined (orphaned thread)
CT->>CT: Show error toast "Couldn't send reply"
CT->>CT: Keep draft text
end
TRPC->>HS: tRPC mutation request
HS->>HS: Validate input (zod: workspaceId, commentId positive int, body trimmed min 1)
HS->>DB: Look up workspace by id
alt Workspace not found or no pullRequestId
DB-->>HS: null / no PR
HS-->>TRPC: NOT_FOUND error
TRPC-->>CT: Error response
CT->>CT: Restore draft + error toast "Couldn't post reply"
else Workspace has pullRequestId
DB-->>HS: Workspace row
HS->>DB: Look up pull request by id
alt PR row not found
DB-->>HS: null
HS-->>TRPC: INTERNAL_SERVER_ERROR
TRPC-->>CT: Error response
CT->>CT: Restore draft + error toast
else PR row found
DB-->>HS: PR row (repoOwner, repoName, prNumber)
HS->>HS: Call replyToReviewComment helper
HS->>GH: POST pulls.createReplyForReviewComment({owner, repo, pull_number, comment_id, body})
alt GitHub accepts reply
GH-->>HS: New comment data
HS-->>TRPC: {id: newCommentId}
TRPC-->>CT: Success
CT->>CT: Invalidate getPullRequestThreads query
CT-->>CT: New comment renders on next refetch
else GitHub rejects (404, 403, etc.)
GH-->>HS: Error with status
HS->>HS: actionRejectionError mapping
HS-->>TRPC: NOT_FOUND / other mapped code
TRPC-->>CT: Error response
CT->>CT: Restore draft (if textarea empty) + toast
end
end
end
Note over CT,HS: Resolution flow still works alongside reply
CT->>TRPC: git.setReviewThreadResolution.mutate()
TRPC->>HS: tRPC mutation request
HS->>GH: Resolve/unresolve thread via GraphQL
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| }), | ||
| ) | ||
| .mutation(async ({ ctx, input }) => { | ||
| const workspace = ctx.db.query.workspaces |
There was a problem hiding this comment.
P0: Authorize input.workspaceId against ctx.userId before resolving its pull request. As written, any authenticated host member can supply another user's workspace ID and post a review reply through that workspace's linked pull request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/host-service/src/trpc/router/git/git.ts, line 1072:
<comment>Authorize `input.workspaceId` against `ctx.userId` before resolving its pull request. As written, any authenticated host member can supply another user's workspace ID and post a review reply through that workspace's linked pull request.</comment>
<file context>
@@ -1053,4 +1054,51 @@ export const gitRouter = router({
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ const workspace = ctx.db.query.workspaces
+ .findFirst({ where: eq(workspaces.id, input.workspaceId) })
+ .sync();
</file context>
… test suite Review follow-ups on #7166: - CommentThread.test.tsx crashed the desktop suite on CI with "Unhandled error between tests". Two earlier suites replace `react` with a hooks-only stub for the rest of the process, and this test was the first afterwards to load react-syntax-highlighter (through CommentMarkdown), whose CommonJS build extends React.PureComponent at load time. Mock CommentMarkdown: the bodies are irrelevant to the reply flow. - Extract ReviewThreadReplyComposer (textarea, Cmd/Ctrl+Enter, Reply button, no-target toast) and use it from both the Changes pane's CommentThread and the PR view's PullRequestCommentThread, so the two stop drifting. Each caller keeps its own dispatch and draft state. - Cover the "workspace points at a pull request row that no longer exists" path in the replyToReviewThread test. Claude-Session: https://claude.ai/code/session_01DQF36hf8kmYHRggDxFwXuk
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/ReviewThreadReplyComposer/ReviewThreadReplyComposer.tsx">
<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/ReviewThreadReplyComposer/ReviewThreadReplyComposer.tsx:64">
P3: The reply textarea has no accessible name because its placeholder is the only identifying text. Add a localized `aria-label` (or an associated label) so screen-reader users can distinguish the reply control.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| return ( | ||
| <div className={cn("flex flex-col gap-2 border-t py-2", className)}> | ||
| <Textarea |
There was a problem hiding this comment.
P3: The reply textarea has no accessible name because its placeholder is the only identifying text. Add a localized aria-label (or an associated label) so screen-reader users can distinguish the reply control.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/ReviewThreadReplyComposer/ReviewThreadReplyComposer.tsx, line 64:
<comment>The reply textarea has no accessible name because its placeholder is the only identifying text. Add a localized `aria-label` (or an associated label) so screen-reader users can distinguish the reply control.</comment>
<file context>
@@ -0,0 +1,93 @@
+
+ return (
+ <div className={cn("flex flex-col gap-2 border-t py-2", className)}>
+ <Textarea
+ value={value}
+ onChange={(e) => onChange(e.target.value)}
</file context>
Summary
git.replyToReviewThreadmutation. The GitHub call it shares withpullRequests.replyToThreadmoves into one helper so the two surfaces can't drift.Why / Context
Review threads already rendered inline in the Changes pane with resolve/unresolve, but answering one meant leaving for the PR view or GitHub. The PR view had the full reply flow, so this ports that implementation over rather than inventing a second one.
How It Works
git.getPullRequestThreadsalready returns each comment's RESTdatabaseId;useDiffAnnotationsByPathnow puts the first comment's id on the thread metadata asreplyToCommentId.CommentThreadowns agit.replyToReviewThreadmutation. On send it clears the draft; on success it invalidates the workspace threads query, and the annotation version hash already includes comment count, so the new comment renders on the next refetch. On failure it toasts and hands the draft back (unless a new one was started).git.replyToReviewThreadlooks up workspace → linked PR row and posts viapulls.createReplyForReviewCommentusing the PR row's repo owner/name/number. A comment id from another PR 404s rather than landing somewhere unexpected.replyToReviewComment(new,git/utils) wraps the octokit call plusactionRejectionErrormapping;pullRequests.replyToThreadis refactored onto it.Manual QA Checklist
Not run against the live app: no dev app was running from this worktree, and posting a real reply to GitHub is outward-facing. Worth a pass before merge:
Testing
bun run typecheck(host-service and desktop)bun test src/trpc/router/git src/trpc/router/pull-requestsin host-service (145 pass). Newreply-to-review-thread.test.ts: happy path, blank body rejected before GitHub, no linked PR → NOT_FOUND, GitHub 404 → NOT_FOUNDbun teston the DiffPane tree in desktop (30 pass). NewCommentThread.test.tsx: submit + clear + invalidate, Cmd+Enter, disabled states, in-flight guard, orphaned thread keeps its draft, failure restores the draftbun run check:i18nclean. Every string the reply box uses already exists in all 17 locales from the PR view, so no catalog changesbun testin packages/i18n (enforced-dirs, display-only) andbiome checkon the changed filesDesign Decisions
resolveGithubRepofor the reply target:getCheckJobLogsalready uses the PR row; it needs no remote parsing and names exactly the repo the comment ids came from.Follow-ups
git.setReviewThreadResolutionandpullRequests.setThreadResolution. The same shared-helper treatment would fit; left out to keep this focused.https://claude.ai/code/session_01DQF36hf8kmYHRggDxFwXuk
Summary by cubic
Adds reply support for PR review threads in the workspace Changes pane, matching the PR view's Code tab. You can now type a reply in each thread and submit it to GitHub; previously the pane only allowed resolving/unresolving threads.
New Features
git.replyToReviewThreadmutation posts the reply to the linked PR.Refactors
ReviewThreadReplyComposer, used by the Changes pane and the PR view's Code tab.pullRequests.replyToThreadand the new mutation.Written for commit 36ff9a6. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Tests