Fix token-split message issue on session reload#7962
Conversation
After streaming completion, accumulate text deltas in a buffer instead of persisting each one individually. This prevents each token from appearing as a separate message when reopening Goose. The pending message is flushed when tool requests are encountered or after the stream ends. Fixes the regression from commit bc7e063.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af13ebd850
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Each streaming text delta only contains a single token/word, not the full text. The previous fix stored the last delta message, which would truncate the response to just the final token. Now we accumulate all delta text into a buffer and replace the message content with the full text at flush.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e238a76fd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Flush any pending text message before processing tool requests | ||
| if let Some(mut text_msg) = pending_text_message.take() { | ||
| // Replace delta content with full accumulated text | ||
| text_msg.content = vec![MessageContent::text(&accumulated_text)]; |
There was a problem hiding this comment.
Preserve non-text assistant content when flushing text buffer
Replacing text_msg.content with a single Text block drops any non-text assistant content that arrived in the buffered message. This is observable with providers that stream reasoning separately from text (for example, responses_api_to_streaming_message can emit a final no-tool message containing MessageContent::Reasoning), so after this flush the persisted conversation loses reasoning data on reload and for subsequent turns even though downstream formatters (e.g. OpenAI formatting) consume reasoning content when present.
Useful? React with 👍 / 👎.
Summary
Fixed regression from commit bc7e063 where each streaming text delta was individually persisted to the database, causing every token to appear as a separate message when reopening Goose.
Instead of immediately persisting each text delta, the fix accumulates text messages in a buffer and flushes the final complete message:
This preserves live streaming UI updates while ensuring only the final message is stored in the database.
Testing
Existing tests pass (176 passed). Manual testing confirms streaming messages no longer split into individual tokens on session reload.
Related Issues
Regression from #7850