From d8e1aff7eeffa8c451ee17067c0c3b6e08b6adf3 Mon Sep 17 00:00:00 2001 From: Nora Date: Tue, 10 Mar 2026 05:06:33 +0000 Subject: [PATCH] fix(slack-stream): always deliver fallback on streaming failure regardless of orphan deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a streaming append/start call fails and chat.delete also fails, the stream message is left in 'streaming' state — never finalized via chat.stopStream, which may render as invisible or broken on mobile Slack. streamSession.stopped is already set to true so the end-of-dispatch finalizer also skips the stream, leaving the payload with no recovery path. Remove the orphanDeleted guard from the deliverWithStreaming catch block: always call deliverNormally here even if deletion failed, to ensure the user receives the complete answer. A cosmetic duplicate on desktop clients is preferable to a silently truncated answer. The guard is intentionally kept in the finalizer catch: there the stream message has already been fully finalized (all content visible), so skipping deliverNormally on deletion failure avoids a true content duplicate. --- src/slack/monitor/message-handler/dispatch.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/slack/monitor/message-handler/dispatch.ts b/src/slack/monitor/message-handler/dispatch.ts index 81f0571587c..44ee6fc6604 100644 --- a/src/slack/monitor/message-handler/dispatch.ts +++ b/src/slack/monitor/message-handler/dispatch.ts @@ -335,13 +335,18 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag // Re-deliver the full content: everything already in the stream message // plus the current payload that failed to append. Using only `payload` // here would drop all previously-streamed text. - if (orphanDeleted) { - const fallbackText = streamedText ? `${streamedText}\n${text}` : text; - await deliverNormally( - { ...payload, text: fallbackText }, - streamSession?.threadTs ?? plannedThreadTs, - ); - } + // + // Note: we deliver even when orphan deletion failed. The stream message + // is stuck in "streaming" state (never finalized via chat.stopStream) + // and may not render on mobile Slack — skipping deliverNormally here + // would silently drop content with no later recovery path (the + // finalizer is skipped because streamSession.stopped is already true). + // A cosmetic duplicate on desktop is preferable to a truncated answer. + const fallbackText = streamedText ? `${streamedText}\n${text}` : text; + await deliverNormally( + { ...payload, text: fallbackText }, + streamSession?.threadTs ?? plannedThreadTs, + ); } };