fix(slackbot): keep streamed answer when a plan block is finalized (Codex answers dropped)#397
Open
jalagrange wants to merge 1 commit into
Open
Conversation
Slack streaming works as startStream -> appendStream (chunks) -> stopStream (final blocks). The stopStream `blocks` REPLACE the streamed message body rather than appending to it. In closeTextStream() the answer markdown was only included in the final blocks when it was NOT streamed live (`!streamedTextLive`), on the assumption the accumulated stream chunks would stand. That holds only when no blocks are sent. When a plan block is emitted in the final layout (plan-emitting harnesses like Codex always render a "Thinking" plan), stopStream is called with those blocks, which replace the streamed body and drop the live-streamed answer. The user is left with a Thinking widget and no answer. Claude Code is unaffected because it does not emit a plan block, so stopStream is called with no blocks and the streamed answer survives. Fix: when we emit any composed blocks (e.g. a plan block), also re-include the answer markdown, since those blocks replace the streamed chunks. Only omit the answer blocks when sending no blocks at all and the answer was already streamed live. Verified end-to-end on a live deployment: a Codex Slack reply that previously rendered blocks [rich_text, plan] (no answer) now renders [rich_text, plan, rich_text, table] with the answer present. All existing agent-session and codex-session tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When the Codex harness produces a final answer, the Slack reply shows only the "Thinking" widget and no answer text. The agent completes successfully and the answer is streamed, but it never appears in the closed message. The Claude Code harness is unaffected.
Concretely, the bot message ends up with blocks
[rich_text, plan](header + Thinking) and the answer is gone.Root cause
Slack streaming is
startStream→appendStream(chunks) →stopStream(finalblocks). ThestopStreamblocksreplace the streamed message body; they do not append to the accumulated chunks.In
closeTextStream()(services/slackbot/src/slack/agent-session.ts), the answer markdown was only included in the final blocks when it was not streamed live:That is correct only when
stopStreamis called with no blocks (the accumulated stream chunks then stand on their own). But plan-emitting harnesses (Codex always renders a "Thinking" plan block) causestopStreamto be called with a plan block, which replaces the streamed body and drops the live-streamed answer.Claude Code does not emit a plan block, so
stopStreamis called with no blocks and its streamed answer survives — which is why only Codex is affected.Fix
When we emit any composed blocks (e.g. a plan block), also re-include the answer markdown, since those blocks replace the streamed chunks. Only omit the answer blocks when sending no blocks at all and the answer was already streamed live.
Verification
Verified end-to-end on a live deployment. A Codex Slack reply that previously rendered
[rich_text, plan](no answer) now renders[rich_text, plan, rich_text, table]with the answer table present.All existing
agent-sessionandcodex-sessiontests pass (45 across the two suites). The existing "does not duplicate live plan or streamed answer markdown on finalize" test still passes — its scenario has the plan already streamed (planStarted=true), so no plan block is emitted at finalize and the answer is correctly left as the streamed chunks.I attempted a focused unit regression test, but the exact trigger state (a plan/Thinking block emitted at finalize while the answer streamed live) depends on reasoning-summary publish ordering that was non-trivial to reproduce synthetically at the unit level. Happy to add one with guidance on the preferred test shape.