diff --git a/ui/src/pages/chat/chat-pane-attachment-handoff.test.ts b/ui/src/pages/chat/chat-pane-attachment-handoff.test.ts index 0b78ea2c5241..16e1ef8a13b0 100644 --- a/ui/src/pages/chat/chat-pane-attachment-handoff.test.ts +++ b/ui/src/pages/chat/chat-pane-attachment-handoff.test.ts @@ -107,9 +107,12 @@ describe("staged chat attachment pane handoff", () => { ); closePaneStagedAttachments(pane.context, root, layout, pane.paneId); + const lateAttachment = storedAttachment("late-close-completion"); + current.chatAttachments.push(lateAttachment); pane.disconnectedCallback(); expect(getChatAttachmentDataUrl(fallback)).toBeNull(); + expect(getChatAttachmentDataUrl(lateAttachment)).toBeNull(); expect( pane.context.chatAttachmentHandoff.consume({ owner, diff --git a/ui/src/pages/chat/chat-pane-lifecycle.ts b/ui/src/pages/chat/chat-pane-lifecycle.ts index e1da41d5495a..82d830c75975 100644 --- a/ui/src/pages/chat/chat-pane-lifecycle.ts +++ b/ui/src/pages/chat/chat-pane-lifecycle.ts @@ -670,13 +670,19 @@ export abstract class ChatPaneLifecycle extends ChatPaneBoard { } override disconnectedCallback() { - if (this.state && !this.suppressStagedAttachmentHandoffOnDisconnect) { - preparePaneStagedAttachments( - this.context, - this.paneId, - this.state, - this.stagedAttachmentGatewayOwner, - ); + if (this.state) { + if (this.suppressStagedAttachmentHandoffOnDisconnect) { + // MCP app teardown can delay DOM removal after pane close. Finalize any + // attachment that completed during that delay instead of leaking it. + discardStateStagedAttachments(this.state); + } else { + preparePaneStagedAttachments( + this.context, + this.paneId, + this.state, + this.stagedAttachmentGatewayOwner, + ); + } } this.stagedAttachmentGatewayOwner = null; this.clearComposerPrefillAttention();