Skip to content

feat(github-mcp): add comment_edit to edit existing comments - #21

Merged
Martin Bens (SpiGAndromeda) merged 5 commits into
mainfrom
feat/comment-edit
Oct 6, 2026
Merged

Martin Bens (SpiGAndromeda) merged 5 commits into
mainfrom
feat/comment-edit

Conversation

@SpiGAndromeda

@SpiGAndromeda Martin Bens (SpiGAndromeda) commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

The plugin could create comments but not correct them. issue_comment, pr_comment and pr_review_reply only post new ones, the write server's api tool sends no request body, and the hook blocks gh pr comment --edit-last. This adds the write tool comment_edit, which replaces the body of an existing comment given its URL.

comment_edit {"url":"https://github.com/OWNER/REPO/pull/128#discussion_r4193579918","body":"Fixed wording."}
→ https://github.com/OWNER/REPO/pull/128#discussion_r4193579918

comment_edit {"url":"OWNER/REPO/pull/128#issuecomment-4581345917","body":"…"}
→ Error: comment_edit: the comment was written by github-actions[bot], not by you; nothing was edited. Pass allow_other_author: true to edit it

Changes

  • The URL picks the endpoint (tool_comment_edit in lib/review_write.sh): GitHub numbers issue comments, inline review comments and reviews in separate sequences, so a bare ID does not say which endpoint to call. The anchor does: #issuecomment-ID goes to PATCH issues/comments/ID, #discussion_rID (or #rID in the Files, Changes and commit views of a PR) to PATCH pulls/comments/ID, and #pullrequestreview-ID to PUT pulls/N/reviews/ID. The URL works with or without https://github.com/, a notification link's query string is ignored, and any other URL fails before a request.
  • Read before writing: the tool reads a conversation or inline comment first and refuses it when it does not belong to the issue or PR named in the URL, so a wrong number cannot edit a comment somewhere else. A review summary is read through pulls/N/reviews/ID, which already carries the PR number.
  • Own comments by default: for a submitted comment or review summary, the tool asks GitHub through viewerDidAuthor whether the caller wrote it, and refuses otherwise. allow_other_author: true skips the check, so moderation edits stay possible.
  • Pending review comments: the REST endpoint answers 404 for a comment in an unsubmitted review. On a 404 for an inline comment, the tool looks the comment up in the PR's pending reviews through GraphQL and edits it with the updatePullRequestReviewComment mutation. The 404 and the lookup's result both appear in the error when nothing matches.
  • No fallback or suppress_errors: every other tool declares both, but fallback would turn a failed edit into a success, so comment_edit declares neither and every failure is a tool error.
  • Hooks: check-api-tools.sh (the api tool, under block_api_tool_write) and check-gh-tools.sh (raw gh api, under block_api_commands) send PATCH on issues/comments/ID and pulls/comments/ID and PUT on pulls/N/reviews/ID to comment_edit. The block messages for gh pr comment and gh issue comment name it for editing an existing comment.
  • Tool description: it avoids the words "review" and "reviews", because with them pi's tool_search ranks pr_reviews below its allowed rank. REFERENCE.md uses the plain terms.
  • Docs: REFERENCE.md documents the URL forms, the checks and the errors; the README, SETUP.md and its plugin-setup copy, the SessionStart directive and the MCP issue template list the tool. The plugin-setup CHANGELOG.md records the new permission pattern.

Known limits

  • Commit comments and Discussions comments are not supported.
  • A comment found in a pending review gets no author check from the tool; whether the edit is allowed is left to GitHub's mutation. The lookup reads the first 100 pending reviews of the PR and up to 50 pages of comments.
  • The author check has only been run with a user token. With a GitHub App or Actions token, viewerDidAuthor is untested; if it refuses the token's own comment, allow_other_author: true is the way around it.

Agents could create comments but not correct them: the `api` write tool sends no request body, and the `gh` hook blocks `gh pr comment --edit-last`. `comment_edit` takes the comment's URL and a new body. The URL anchor decides the call, because GitHub keeps issue comments, inline review comments and review summaries in separate id sequences, so a bare numeric id cannot identify the kind.

`#issuecomment-ID` goes to `PATCH issues/comments/ID`, `#pullrequestreview-ID` to `PUT pulls/N/reviews/ID`, and `#discussion_rID` or `/files#rID` / `/changes#rID` to `PATCH pulls/comments/ID`. REST answers 404 for a comment in the caller's unsubmitted review, so for inline anchors the tool first queries the caller's pending review over GraphQL and, on a match, edits it with `updatePullRequestReviewComment`.

Every failure, including a lookup error, a missing URL in GitHub's response, or more than 100 pending comments without a match, returns an error. No route falls back to another. The tool declares no `fallback` or `suppress_errors`, since `fallback` would report a failed edit as a success.

`check-api-tools.sh` redirects the three edit endpoints on the `api` tool to `comment_edit`, and the `gh pr comment` / `gh issue comment` block messages name it.

Co-Authored-By: Claude <noreply@anthropic.com>
`comment_edit` used the issue or PR number in the URL only to build the review-summary endpoint, so `issues/7#issuecomment-555` edited comment 555 even when it belonged to issue 99. The tool now reads a conversation or inline comment first and edits it only when its `issue_url` ends in `/issues/N` or its `pull_request_url` in `/pulls/N`. Otherwise it errors and writes nothing.

The read also picks the route for inline comments. The previous version queried the pending review over GraphQL before every inline edit and failed hard on a lookup error or on more than 100 pending comments, which blocked edits of submitted comments that REST alone would complete. Now a successful read leads to the REST `PATCH`, and only a 404 leads to the pending-review lookup. The lookup matches `fullDatabaseId` as a string, takes the caller's own review via `viewerDidAuthor` among up to 100 pending reviews, and errors when nothing matches.

Every gh call captures stdout and stderr apart through `_gh_capture_split`, so a stderr warning on a successful mutation no longer reports an applied edit as failed. One helper, `_comment_edit_graphql`, classifies GraphQL failures for the lookup and the mutation, and every error starts with `Error:`.

The `check-api-tools.sh` message for `PATCH issues/comments/ID` pointed to `pr_comments`, which never returns conversation comments. It now points to `issue_view` or `pr_view` with `fields=comments`. Three tests that only checked a static prompt or message for a word are removed.

Co-Authored-By: Claude <noreply@anthropic.com>
…it URLs

A pending review with more than 100 comments made `comment_edit` return an error instead of finding the comment, so an edit the tool could complete failed. The lookup now reads further pages of the caller's pending review by `endCursor`, and errors when a page fails, when `hasNextPage` comes with no cursor, or when a cursor it already used comes back, which stops both a repeated cursor and a cycle such as A, B, A.

The URL parser rejected owners with `_`, which Enterprise Managed User accounts carry (`mona_octocorp`), and accepted numbers with leading zeros that then failed the ownership check with a contradictory message. Owners may now contain `_`, and a number or ID starting with `0` is rejected before any request.

When the inline-comment read answers 404 and the lookup fails or finds nothing, the error now states the 404 as well, so a typo in the repository name no longer shows up only as a GraphQL message. Every gh call runs through one helper that builds the error text in one place, and the mutation's jq call is guarded so the output always starts with `Error:`.

The tool description names all accepted URL prefixes and says it returns the edited comment's URL, which matches the pending-review path. Two tests that only matched query text are removed.

Co-Authored-By: Claude <noreply@anthropic.com>
…thor is set

With a maintainer token, `comment_edit` edited any comment on the issue or PR named in the URL, so a wrong URL silently rewrote a contributor's text under their name. The tool now compares the comment's author with the caller's login from GraphQL `viewer { login }` and refuses a mismatch unless the new boolean `allow_other_author` is true, which keeps moderation possible on request. The review summary is now read before its `PUT` for the same check, and a comment in the caller's own pending review needs no check.

A write that GitHub accepted but answered without a URL was reported as a failed edit, which invites a retry of an edit that already landed. It stays an error but now says the edit was sent.

Links copied from GitHub now work. A query string such as `?notification_referrer_id=` is ignored, and inline comments are accepted on `pull/N/files/<sha>..<sha>` and `pull/N/commits/<sha>`. The README, CHANGELOG and SessionStart wording no longer promise every comment kind. Commit and Discussions comments are not supported. The two inline anchor branches are merged, and a test that could not fail is removed.

Co-Authored-By: Claude <noreply@anthropic.com>
Comparing the comment's REST `user.login` with GraphQL `viewer.login` may disagree for App and Actions tokens, which would refuse a bot's edit of its own comment. `comment_edit` now asks GitHub directly through `viewerDidAuthor` on the comment's node and refuses anything but `true` unless `allow_other_author` is set.

Several guards protected against responses GitHub does not send or against URLs it never produces, and each would have failed loudly without them. Cursor cycle detection is replaced by a 50-page limit, the pending-review author filter and the more-than-100-pending-reviews error are gone (whether another user's pending comment may be edited is left to GitHub's mutation), malformed-response branches collapse into fewer errors, and `.git` and leading-zero rejections are dropped. The `.`/`..` repo rejection stays, since `repos/o/../...` would reach a different endpoint.

Links copied from GitHub's Files and Changes views of a single commit or a range now work, and the host is matched case-insensitively. `check-gh-tools.sh` now redirects raw `gh api` edits of comments and review summaries to `comment_edit` under `block_api_commands`. Before, these calls were not redirected to `comment_edit`, and one form of the review `PUT` went to `pr_reviews`.

The CHANGELOG entries describe what users can do instead of how the tool works inside, REFERENCE.md lost claims the code no longer backs, the plugin-setup CHANGELOG records its new permission pattern, and the URL-form tests are one table.

Co-Authored-By: Claude <noreply@anthropic.com>
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