Skip to content

The comment you can take back - #19

Merged
necessarylion merged 1 commit into
mainfrom
the-comment-you-can-take-back
Jul 29, 2026
Merged

necessarylion merged 1 commit into
mainfrom
the-comment-you-can-take-back

Conversation

@necessarylion

Copy link
Copy Markdown
Owner

Every other thing said on a pull request could be changed from this window — the
title, the description, a review's verdict — except the one thing said most
often. A typo in a review comment meant leaving for the browser.

What it does

Edit sits next to Reply and Resolve, on a comment in the conversation and on
one hanging off a line of the diff alike. It opens the box in place of the
comment's text rather than under it: what is in the box is the comment, and
drawing both reads as two of them. The header stays, so it still says whose it
is and when. Cancel and Save are the box's own, so the row of verbs underneath
goes away while it is open — the same ⌘↩ posts it and the same ⎋ puts it
down, and @ names people out of the same list.

Who gets the button

The host's answer, not a guess.

GitHub says so itself: every comment in GraphQL carries viewerCanUpdate, true
for a maintainer on somebody else's comment too, exactly as the host's own page
behaves. That is not a second call — the query that already reads resolution per
thread now reads the conversation's comments and the review summaries beside it,
so one request covers all three kinds. It is allowed to fail the way it already
was: no gh, no scope, an older enterprise schema, and nothing offers an edit.

Bitbucket has no such flag on either flavour, so the account is looked up and
matched by hand. BitbucketIdentity asks once per checkout and keeps the answer:
Cloud's /2.0/user gives an account id, which is what a comment carries too, and
Data Center has only the handle, which is what its comments carry. A Cloud token
without the account scope falls through to bkt auth status, whose handle is
the username — and a Cloud comment is signed with the nickname, so nothing
matches and no comment offers an edit. Same shape of retreat resolution already
makes, and why a failed lookup is not cached.

How each host is asked

Comment kind Call
GitHub conversation comment GraphQL updateIssueComment
GitHub review summary GraphQL updatePullRequestReview
GitHub inline review comment REST PATCH .../pulls/comments/{id}
Bitbucket, both flavours PUT on Cloud's comment endpoint, falling back to Data Center's — which also gets the version it insists on

gh pr view reports a conversation comment and a review by their GraphQL node
ids and nothing else; an inline review comment already has a REST id in hand.
Rather than leave the writer to work out which from the shape of an id, the
loader — the only thing that ever knew — records it as a
PullRequestComment.EditTarget.

Two details worth the reading

An edit starts from what the host stores, not from what is on screen.
Bitbucket Cloud writes a mention as an account id and the app puts the person's
name in its place for reading; saving that back would post the name as plain
words and quietly drop the mention. The raw text is kept beside the rendered one
for exactly the comments where the two differ, and the way out goes through the
same mention encoding a new comment does.

The reload is what closes the box. Afterwards the conversation is read again
rather than patched locally, for the reason a resolve does the same: the host's
copy is the one that counts, and it carries whatever "edited" mark the host adds.
The comment that comes back says something else, so it is no longer the one being
edited. An edit the host refused leaves the comment as it was, so the box stays
open with the text still in it — which is the point of not clearing it by hand.

Checked

Scripts/run.sh — build succeeded, no warnings, app launches.

Both GraphQL mutations were run against the live API with a deliberately bogus
node id: the fields resolve and only the id is rejected, and gh exits non-zero
on a GraphQL error, so a refusal reaches the box. The id spellings were confirmed
to match on a real pull request — gh pr view --json comments and the GraphQL
comments.nodes.id return the identical node id, which is what the lookup keys
on. The inline REST path and bkt api /2.0/user were both confirmed to resolve.

Not checked: an edit that actually lands. That means writing to a real
comment on a real repository, so it is left to review.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WgLkUJ7ZK3rd3zZ1HhhH9t

Every other thing said on a pull request could be changed here — the title, the
description, a review's verdict — except the one thing said most often. A typo
in a review comment meant leaving the window for the browser.

**Edit sits next to Reply and Resolve**, on a comment in the conversation and on
one hanging off a line of the diff alike. It opens the box *in place of* the
comment's text rather than under it: what is in the box is the comment, and
drawing both reads as two of them. The header stays, so it still says whose it
is and when. Cancel and Save are the box's own, so the row of verbs underneath
goes away while it is open — the same `⌘↩` posts it and the same `⎋` puts it
down, and `@` names people out of the same list.

**Who gets the button is the host's answer, not a guess.** GitHub says so
itself: every comment in GraphQL carries `viewerCanUpdate`, which is also true
for a maintainer on somebody else's comment, exactly as the host's own page
behaves. That is not a second call — the query that already reads resolution per
thread now reads the conversation's comments and the review summaries beside it,
so one request covers all three kinds. It is allowed to fail the way it already
was: no `gh`, no scope, an older enterprise schema, and nothing offers an edit.

Bitbucket has no such flag on either flavour, so the account has to be looked up
and matched by hand. `BitbucketIdentity` asks once per checkout and keeps the
answer: Cloud's `/2.0/user` gives an account id, which is what a comment carries
too, and Data Center has only the handle, which is what its comments carry. A
Cloud token without the `account` scope falls through to `bkt auth status`,
whose handle is the *username* — and a Cloud comment is signed with the
nickname, so nothing matches and no comment offers an edit. That is the same
shape of retreat resolution already makes, and it is why the lookup is not
cached when it fails.

**Three kinds of GitHub comment need three different calls**, which is the one
thing this change could not paper over. `gh pr view` reports a conversation
comment and a review by their GraphQL node ids and nothing else, so those go
through `updateIssueComment` and `updatePullRequestReview`; an inline review
comment already has a REST id in hand and takes a plain `PATCH`. Rather than
leave the writer to work out which from the shape of an id, the loader — the
only thing that ever knew — records it as a `PullRequestComment.EditTarget`.
Bitbucket is one case there because both flavours are one `PUT` tried twice:
Cloud takes the Markdown under `content.raw`, Data Center takes it as `text` and
refuses an edit that does not name the version it replaces, which is read off
the comment when it is loaded.

**An edit starts from what the host stores, not from what is on screen.**
Bitbucket Cloud writes a mention as an account id and the app puts the person's
name in its place for reading; saving that back would post the name as plain
words and quietly drop the mention. The raw text is kept beside the rendered one
for exactly the comments where the two differ, and the way out goes through the
same mention encoding a new comment does.

Afterwards the conversation is read again rather than patched locally, for the
reason a resolve does the same: the host's copy is the one that counts, and it
is what carries whatever "edited" mark the host adds. That reload is also what
closes the box — the comment that comes back says something else, so it is no
longer the one being edited. An edit the host *refused* leaves the comment as it
was, so the box stays open with the text still in it, which is the point of not
clearing it by hand.

Checked: `Scripts/run.sh` — build succeeded, no warnings, app launches. Both
GraphQL mutations were run against the live API with a deliberately bogus node
id: the fields resolve and only the id is rejected, and `gh` exits non-zero on a
GraphQL error, so a refusal reaches the box. The id spellings were confirmed to
match on a real pull request — `gh pr view --json comments` and the GraphQL
`comments.nodes.id` return the identical node id, which is what the lookup keys
on. Not checked: an edit that actually lands, which means writing to a real
comment on a real repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgLkUJ7ZK3rd3zZ1HhhH9t
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@necessarylion, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 043f25de-c98e-4c68-b705-1587f6a4205b

📥 Commits

Reviewing files that changed from the base of the PR and between 4522e53 and 589543a.

📒 Files selected for processing (8)
  • Docs/Development.md
  • README.md
  • Sources/Workspace/Models/PullRequest.swift
  • Sources/Workspace/Models/PullRequestComment.swift
  • Sources/Workspace/Models/WorkspaceStore.swift
  • Sources/Workspace/Views/DiffView.swift
  • Sources/Workspace/Views/InlineCommentViews.swift
  • Sources/Workspace/Views/PullRequestDetailView.swift

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.

@necessarylion
necessarylion merged commit 3e03526 into main Jul 29, 2026
2 checks passed
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