Skip to content

feat(pull-requests): link a PR to its thread from the PR action menu - #8488

Closed
mfattakhov wants to merge 1 commit into
pingdotgg:mainfrom
mfattakhov:mfattakhov/link-pr-from-menu
Closed

feat(pull-requests): link a PR to its thread from the PR action menu#8488
mfattakhov wants to merge 1 commit into
pingdotgg:mainfrom
mfattakhov:mfattakhov/link-pr-from-menu

Conversation

@mfattakhov

@mfattakhov mfattakhov commented Aug 28, 2026

Copy link
Copy Markdown

A thread can be linked to a pull request, but only from the thread side. Standing in the PR detail panel — the place you land from the PR list, from the sidebar PR row, or from a review notification — there is no way to say "this PR belongs to that thread". After unlinking there is no way back at all, because the panel's link affordance was gated on the handoff attach target: once you unlink, the thread stops matching the PR by branch, the panel falls back to page context, and the entry point disappears with it.

This adds link and unlink entries to the PR action menu, driven by the open thread rather than by the attach target, so the entry stays put across link/unlink cycles. It reuses thread.meta.update through a small useThreadPullRequestLinkActions hook — no new command, no contract change.

Why it is worth having: any workflow where the branch a thread worked on is not the branch the PR ends up on — stacked branches, GitButler virtual branches, squash-and-rename flows — makes branch matching miss, so the thread and its PR drift apart. Linking from the PR side is the one place the association is obvious, and it is also what makes the thread settle when the PR merges. Today the only fix is to go find the thread, and a single accidental unlink removes the affordance entirely, which is a one-way door.

Model: Claude Opus 5 (1M context), harness: Claude Code.


Note

Low Risk
UI-only, capability-gated metadata updates through an existing command; no contract or auth changes.

Overview
Adds link / unlink pull request actions to the PR detail panel’s overflow menu when a real thread is open beside the panel and the server advertises threadPullRequestLinking.

Linking keys off the open thread (composerDraftTarget), not the handoff attach target, so the menu stays available after unlink when branch-based matching would drop the panel back to page context.

A new useThreadPullRequestLinkActions hook is the dedicated path to set or clear linkedPullRequest via thread.meta.update. The panel serializes in-flight link/unlink with pending state (stable labels, disabled item), gates on server capability, and shows success/error toasts (suppressing errors for interrupted commands).

isThreadLinkedToPullRequest in pullRequestDetail.logic decides Link vs Unlink using matchesLinkedPullRequestUrl plus an exact URL/number fallback for hosts the parser cannot match; unit tests cover subpages, wrong PR numbers, and self-hosted URLs.

Reviewed by Cursor Bugbot for commit aec1a54. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add link/unlink thread actions to PullRequestDetailPanel menu

  • Adds a conditional menu item to PullRequestDetailPanel.tsx that shows "Link PR to this thread" or "Unlink PR from this thread" when the panel renders beside a concrete thread and the server reports the threadPullRequestLinking capability
  • Introduces isThreadLinkedToPullRequest in pullRequestDetail.logic.ts to detect the linked state via tolerant URL matching, with strict-equality fallback for unparseable hosts
  • Adds the useThreadPullRequestLinkActions hook in useThreadPullRequestLink.ts which updates linkedPullRequest in thread metadata through updateMetadata, suppressing error toasts for interrupted commands and blocking concurrent operations with pending state
  • Behavioral Change: menu items only appear when threadPullRequestLinking server config is enabled; existing panels without a concrete thread context show no new items

Macroscope summarized aec1a54.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 91c91b7e-4b9d-4814-8d90-e89940008e25

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actions github-actions Bot added the vouch:unvouched PR author is not yet trusted in the VOUCHED list. label Aug 28, 2026
@github-actions github-actions Bot added the size:L 100-499 changed lines (additions + deletions). label Aug 28, 2026
Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx Outdated
Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

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.

Two findings on the new thread link/unlink menu items in PullRequestDetailPanel: the capability gate that this PR adds to state/entities.ts is never applied, and the "already linked" test uses strict URL equality instead of the shared change-request URL match used by the other link surface. Details inline.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx Outdated
Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx Outdated
Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This adds a new production Link/Unlink workflow that persists thread metadata and changes how linked threads are represented and settled when the PR changes state. The implementation is localized and reuses existing infrastructure, but the new capability and lifecycle impact exceed a routine UI-only change.

You can add or adjust custom eligibility rules. Learn more.

@mfattakhov
mfattakhov force-pushed the mfattakhov/link-pr-from-menu branch from 3f5b329 to 2c0f115 Compare August 28, 2026 05:59
Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
@mfattakhov

Copy link
Copy Markdown
Author

Both findings were real and are fixed in 2c0f115.

Capability gate. The link/unlink items now render only when the thread's environment reports threadPullRequestLinking. The read is reactive (useServerConfigs() keyed by attachThreadRef.environmentId), because this popup is React-rendered and capabilities arrive after connect. readEnvironmentSupportsPullRequestLink in state/entities.ts is gone from this PR — the non-reactive reader had no caller here, so it was dead code rather than a gate.

Match too strict. isLinkedToThisPullRequest now uses matchesLinkedPullRequestUrl, the same host + repository + number match the thread side uses, so a link stored from an agent-written /pull/42/files href resolves to the canonical PR and the menu offers Unlink rather than a second Link.

Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx Outdated
@mfattakhov
mfattakhov force-pushed the mfattakhov/link-pr-from-menu branch from 2c0f115 to efde976 Compare August 28, 2026 06:13
@mfattakhov

Copy link
Copy Markdown
Author

Two more findings from the second pass, both fixed in efde976.

  • Concurrent link/unlink. thread.meta.update is last-write-wins, so two of these in flight could land reversed and leave the thread on the earlier choice. The menu item now holds an in-flight lock: one request at a time, and the label reads "Linking..." / "Unlinking..." while it runs.
  • Unparseable hosts never offering Unlink. This menu stores detail.url verbatim, so a host parseChangeRequestUrl does not recognise would link once and never show the way back. isLinkedToThisPullRequest now falls back to exact number + URL equality when the shared match cannot parse both sides.

The "Link menu skips capability gate" comment re-anchored onto the new commit but its cited locations still point at 3f5b329; the gate landed in 2c0f115 (supportsPullRequestLink, read reactively from the thread environment's threadPullRequestLinking) and is still in place.

@mfattakhov
mfattakhov force-pushed the mfattakhov/link-pr-from-menu branch from efde976 to 0aea50a Compare August 28, 2026 08:52
@mfattakhov

Copy link
Copy Markdown
Author

All bot checks are green on this branch. The remaining approvability note was about coverage, so 0aea50a adds it: the menu's linked/not-linked decision moved into pullRequestDetail.logic.ts as isThreadLinkedToPullRequest, with tests for a link stored from a subpage, a different PR in the same repository, a self-hosted host the change-request parser cannot read (the exact-URL fallback), and the missing-either-side cases.

@mfattakhov
mfattakhov force-pushed the mfattakhov/link-pr-from-menu branch from 0aea50a to f836754 Compare August 28, 2026 13:27

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f836754b596fa9e03a84166b830dfbee27f88d7a. Configure here.

Comment thread apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
A thread could already be linked to a pull request, but only from the thread
side. Standing in the PR detail panel — the place you land from the PR list or
from a review notification — there was no way to say "this PR belongs to that
thread", and after unlinking there was no way back at all.

Add link and unlink entries to the PR action menu, driven by the open thread
rather than the handoff attach target. Gating on the attach target would strand
the reader: once they unlink, the thread stops matching the PR by branch, the
panel falls back to page context, and the way back would vanish with it.
@mfattakhov

Copy link
Copy Markdown
Author

Fixed in aec1a54fb: the pending state now records which operation is running ("link" | "unlink" | null) rather than a bare boolean. The thread shell picks up the new link before the command settles, so a label read off the live state flipped to the opposite verb mid-flight. The running operation now outranks live state until it settles, and the item keeps the verb the reader pressed.

@juliusmarminge

Copy link
Copy Markdown
Member

Superseded by #10839, which landed link/unlink in the PR More menu (and multi-PR linking). Closing this earlier single-link menu attempt as superseded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants