Agent posted a review comment on an unrelated repository using a fabricated GraphQL node id

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Mode: agent, local, sandboxed shell with gh allowlisted
GitHub CLI: gh 2.96.0, authenticated with a classic token (scopes: gist, read:org, repo, workflow)

Summary
While replying to a review thread on my own PR, the agent constructed a GraphQL
review-thread id instead of reading it from a query. The id resolved to a thread
on a repository in a different organization that I have no involvement with, and
the reply was posted there under my account.

What happened

  1. Task: address review comments on a PR in my organization’s repo.
  2. Earlier in the session the agent correctly ran a gh api graphql
    reviewThreads query and used the returned ids to reply to six threads.
  3. On a follow-up comment, the agent skipped the query and called
    addPullRequestReviewThreadReply with a pullRequestReviewThreadId that was
    never returned by any query. It shared the prefix of the real ids and
    differed in the last few characters.
  4. GitHub accepted it. The reply landed on a PR in an unrelated organization,
    authored by me. The comment text was Kubernetes/ArgoCD content with no
    relation to that PR.
  5. The paired resolveReviewThread call failed with FORBIDDEN, which is what
    surfaced the mistake. The agent then queried the correct id and posted to
    the right PR.
  6. A later DELETE on the stray comment returned 404; I could not remove it.

Impact
A comment appeared under my GitHub account on a third party’s PR with no
relation to my work. I had no way to undo it.

The reply body is taken from the agent session, which can include internal
architecture, unreleased changes, customer context, or credentials discussed
in a private chat. A fabricated node id turns that private context into a
public comment on a third-party pull request, authored by the user, with no
confirmation and no reliable way to delete it. In this case the text was
infrastructure configuration rather than a secret; the same path would
publish privileged content if it had been in the reply.

Steps to Reproduce

Not deterministic; it depends on the model generating

Expected Behavior

The agent should only use resource ids obtained from a prior read in the same
session, and should verify the target repository and PR number on a write that
uses an opaque id. An unverified id should not be sent with user credentials.

Operating System

MacOS

Version Information

Version: 3.20.21
VS Code Extension API: 1.128.0
Commit: f09fca384ceca23f7bf21f9c23655b162641d740
Date: 2026-09-13T19:02:51.781Z
Layout: IDE
Build Type: Stable
Release Track: Default
Electron: 42.10.0
Chromium: 148.0.7778.280
Node.js: 24.18.1
V8: 14.8.178.38-electron.0
xterm.js: 6.1.0-beta.291
OS: Darwin arm64 25.6.0

For AI issues: which model did you use?

composer-2.5

For AI issues: add Request ID with privacy disabled

Request ID: 03155901-7cbb-4c8e-8fc2-970880348eef
Share data was off during this turn however and the error is not reproducible.

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hey @Ben_Leach, thanks for the report, and sorry about the stray comment on someone else’s PR.

I’ve flagged this to the team. The id in that reply was never returned by a query, so the agent should not have used it. The command also ran without asking you because the gh entry on your allowlist covers every gh subcommand, including gh api graphql calls that write to GitHub.

To remove the comment:

  1. Open GitHub search for pull requests with is:pr commenter:@me, sorted by recently updated.
  2. Open the unrelated PR and find your reply in the review thread.
  3. Click the “…” menu on the reply and choose Delete.

The earlier DELETE most likely returned 404 because it was sent against your own repository’s path, not the repository where the comment actually lives.

To stop this from happening again:

  1. In Cursor Settings → Agents → Auto-Run, replace the bare gh allowlist entry with the read-only commands you want auto-run, for example gh pr view, gh pr list and gh pr checks.
  2. Leave gh api off the list, since it covers both reads and writes.
  3. For sessions where the agent replies on GitHub, switch Auto-Run to Ask Every Time so you see each write before it goes out.
  4. Optionally add a User Rule (Cursor Settings → Rules): “Never construct or edit GitHub node ids. Before any gh api graphql mutation that takes an id, query that id first and confirm it belongs to the PR we are working on.”

Colin’s allowlist changes are the right first step. One more layer worth adding, because it doesn’t depend on the model behaving: the token.

A classic token with repo scope can write to any public repo on GitHub. That’s why GitHub accepted a reply on an org you have nothing to do with. From GitHub’s own docs, only classic tokens have write access to public repos outside your own account and orgs.

If the agent’s gh runs on a fine-grained token instead, with resource owner = your org and only the repos you work on (Pull requests: write, Contents: read), the same fabricated ID would have come back as an error instead of a public comment.

Two caveats. Fine-grained tokens can’t span multiple orgs, so you may need one per org. And it doesn’t stop a wrong-PR write inside your own org. For that, the check you described in “Expected Behavior” is the right one: only use IDs that came back from a read in the same session.

Also, I agree with your point about private context. The reply body is the session, so a wrong target is a leak, not just noise.