| … | … | @@ -362,6 +362,13 @@ export function usePullRequestHandoffs({ | |
| 363 | 363 | return; | |
| 364 | 364 | } | |
| 365 | + | // The draft carries the pull request until its first send makes it a thread, which links it. | |
| 366 | + | useComposerDraftStore.getState().setDraftThreadContext(pointed.draftId, { | |
🟡 Medium pullRequest/usePullRequestActions.ts:366
If the user sends the initially opened draft while prepareThread.run is pending, the checkout flow links the PR context to the unrelated new draft at pointed.draftId instead of the promoted thread identified by opened.threadId. The prepared thread is therefore never linked, and navigation can leave the user on an empty replacement draft; attach the context to the originally opened/promoted draft or otherwise resolve the promoted thread before creating and selecting a replacement draft.
Also found in 1 other location(s)
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx:1235
setDraftThreadContextonly records the pending link onpointed.draftId. If the user sends the initially opened draft whileprepareThread.runis in flight, that draft is promoted to the server thread identified byopened.threadId; the secondnewThreadcall then creates a newpointeddraft and this code links that unrelated future draft instead. The checked-out, already-created thread is never linked, and the checkout flow can also navigate the user away to the empty replacement draft.
🤖 Copy this AI Prompt to have your agent fix this:
| 1 | In file @apps/web/src/components/pullRequest/usePullRequestActions.ts around line 366: |
| 2 | |
| 3 | If the user sends the initially opened draft while `prepareThread.run` is pending, the checkout flow links the PR context to the unrelated new draft at `pointed.draftId` instead of the promoted thread identified by `opened.threadId`. The prepared thread is therefore never linked, and navigation can leave the user on an empty replacement draft; attach the context to the originally opened/promoted draft or otherwise resolve the promoted thread before creating and selecting a replacement draft. |
| 4 | |
| 5 | Also found in 1 other location(s): |
| 6 | - apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx:1235 -- `setDraftThreadContext` only records the pending link on `pointed.draftId`. If the user sends the initially opened draft while `prepareThread.run` is in flight, that draft is promoted to the server thread identified by `opened.threadId`; the second `newThread` call then creates a new `pointed` draft and this code links that unrelated future draft instead. The checked-out, already-created thread is never linked, and the checkout flow can also navigate the user away to the empty replacement draft. |
pointed is the draft that receives the checkout's branch and worktree (it's the result of pointing a thread at the prepared checkout), so that's the thread doing the PR work and the one that should be linked. When the originally opened draft was already sent mid-checkout, the existing hand-off flow deliberately opens a fresh draft for the checkout rather than repointing a running thread, and that behaviour predates this PR. The sent thread isn't on the PR branch, so linking it would be wrong.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
| … | … | @@ -461,11 +464,35 @@ export interface DraftSessionState { | |
| 490 | + | draft: Pick<DraftSessionState, "branch" | "checkoutPullRequest"> | null | undefined, | |
| 491 | + | ): string | null { | |
| 492 | + | const checkout = draft?.checkoutPullRequest; | |
| 493 | + | return checkout && draft.branch === checkout.branch ? checkout.url : null; | |
🟡 Medium src/composerDraftStore.ts:493
When a send occurs during the post-checkout stale-branch window, checkoutPullRequestUrlToLink returns null, so the promoted thread is created without the checked-out PR link. The helper compares draft.branch with checkoutPullRequest.branch, but the transient pre-checkout write makes them differ; preserve the checked-out branch until fresh status arrives (or otherwise make this matching logic tolerate that stale update).
No longer relevant as of 5cb958f808a890fa659314a5f216f89b1f14cb45
Intentional trade-off. The window is the gap between the checkout response and the background git-status refresh, under a second in practice, while the dialog is still closing. Matching the branch at send time is what stops a draft that the user moved to another branch from linking the PR. Holding the branch "until fresh status arrives" would need the draft to tell a sync write from a user branch change, and both go through setDraftThreadContext. If the user sends inside that window, the PR is still one click away in Link pull request.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
| … | … | @@ -2836,14 +2856,21 @@ export default function ChatView(props: ChatViewProps) { | |
| 2866 | + | // The draft holds the pull request until its first send creates the thread. A draft that | |
| 2867 | + | // was already sent while the checkout ran is a thread now, so it is linked here instead. | |
| 2868 | + | const threadRef = scopeThreadRef(environmentId, threadId); | |
| 2869 | + | if (readThreadShell(threadRef) !== null) { | |
🟡 Medium components/ChatView.tsx:2869
When the draft is sent before its thread-shell update reaches this client, readThreadShell(threadRef) returns null and this branch skips linkCheckoutPullRequest, leaving the newly created server thread permanently unlinked because the later shell update does not re-run this callback. Do not use the local shell lookup as proof that the send has not completed; link after confirming the authoritative thread exists, or retain and retry this pending checkout link when the shell arrives.
No longer relevant as of ad4214c9e6b02b20fe1c541d38626886b67845cb
Fixed in 5d1f6ffb5f. ChatView now records which draft threads a send has already read, in the same step as the read. When a checkout lands on such a draft (or on a promoted draft or an existing thread), it waits for the thread with the bounded waitForThreadShell and then links it. An unsent draft still links at its first send. The decision is a pure helper, checkoutLandedOnSentDraft, with tests in ChatView.logic.test.ts.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.