fix(discord): preserve streamed replies after tool warnings#83844
fix(discord): preserve streamed replies after tool warnings#83844neeravmakwana wants to merge 1 commit into
Conversation
|
Codex review: needs maintainer review before merge. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. Source inspection on current main shows later error finals still enter the draft-preview finalizer and the shared finalizer clears the draft after fallback delivery; the linked issue and PR comments provide the real Discord symptom. PR rating What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. PR egg Rarity: 🥚 common. What is this egg doing here?
Real behavior proof Mantis proof suggestion Risk before merge Maintainer options:
Next step before merge Security Review detailsBest possible solution: Land the focused Discord guard and regression test after exact-head checks; optional visual Discord proof can further reduce the remaining delivery-risk uncertainty. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection on current main shows later error finals still enter the draft-preview finalizer and the shared finalizer clears the draft after fallback delivery; the linked issue and PR comments provide the real Discord symptom. Is this the best way to solve the issue? Yes. The patch preserves standalone error-final cleanup while bypassing the finalizer only after a non-error final delivery has started, which is a narrow fix for the observed lifecycle bug. Label justifications:
Acceptance criteria:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against d75e16a1b920. |
|
Additional local proof from this branch: Local install/update path used: Discord token/live API probe used the repo Focused behavior proof for the reported delivery lifecycle: This proves the fixed runtime preserves the finalized Discord partial-stream preview while delivering the later tool-warning final separately. I did not run a full live Discord channel roundtrip because this environment only had the bot token; no separate user/driver token or target channel smoke configuration was provided. |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Real behavior proof from a live Discord turn, using the fixed local build. Environment:
Live Discord proof:
{"name":"exec","arguments":{"command":"openclaw definitely-not-a-real-subcommand"}}
{"id":"1506118967717003335","content":"delivery survived","timestamp":"2026-05-19T02:19:04.241000+00:00","edited_timestamp":"2026-05-19T02:19:06.968000+00:00","flags":4}Manual observation during the live run: Discord first showed transient command/progress/log content, then that content was edited away and replaced with the final @clawsweeper re-review |
|
Drive-by note from a Discord + Codex streaming setup that hit a similar symptom, not a formal review. This fix matches the order we observed: assistant final first, then a recovered tool-warning final. One edge case I think we should sanity-check: is the reverse order impossible in the current Pi/Codex path? Example sequence:
If that ordering cannot happen, then I think this PR covers the real-world case we saw. If it can happen through a plugin/runtime/tail-dispatch path, a small regression test for warning-first-then-final might be useful. One design thought: I think we should distinguish terminal error finals from recovered/non-terminal tool-warning finals. I can see why terminal error finals use the existing preview finalizer cleanup path: if the turn ends in an error while a draft preview is active, the draft should be discarded/cleared and the error delivered normally. But recovered tool warnings are supplemental status, not the owner of the assistant preview lane, so ideally they would bypass preview finalization/cleanup regardless of ordering. The broader invariant I think we should preserve is: terminal run errors can clean up the live draft, but recovered/non-terminal tool warnings should not clear or cancel the active assistant preview lane. |
|
@clawsweeper automerge |
|
🦞🔧
Draft PRs stay fix-only until GitHub marks them ready for review. Pause with Automerge progress:
|
|
ClawSweeper 🐠 reef update Thanks for the work here. GitHub would not let ClawSweeper push to this branch with the available credentials, so the fix moved to a narrow replacement PR. nothing personal, just permission currents. Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
fish notes: model gpt-5.5, reasoning high; reviewed against 9dcd993. |
Summary
isErrortool-warning final is delivered in the same turn.delivery survivedremains in the finalized preview and the warning is sent separately.Root cause
Discord partial streaming reused the live draft finalizer for every final payload. When Pi produced a normal assistant final followed by an
isErrortool-warning final, the warning could enter the draft fallback path and calldraftStream.clear(), deleting the finalized preview message that held the assistant answer.Fixes #83831.
Why this is safe
The change is scoped to Discord message delivery for later error final payloads after a non-error final delivery has already started. Standalone error finals still use the existing fallback/cleanup path, and normal preview finalization behavior is unchanged for the first assistant final.
Security/runtime controls are unchanged: this does not alter prompt text, model behavior, tool execution, warning generation, permissions, channel routing, or Discord auth. The preservation decision is enforced in runtime delivery state.
Real behavior proof
Behavior addressed: Discord partial-stream final answer should remain visible when a later tool-warning final is delivered.
Real environment tested: local Discord message-handler runtime test exercising the same partial-stream delivery lifecycle with mocked Discord transport; live Discord/Pi credentials were not used in this PR.
Exact steps or command run after this patch:
node scripts/run-vitest.mjs extensions/discord/src/monitor/message-handler.process.test.ts --testNamePattern "keeps finalized previews when later tool warning finals are delivered"Evidence after fix: the targeted test passes with
1 passed | 69 skipped, and the test asserts the preview edit containsdelivery survived,draftStream.clear()is not called, the draft message id remainspreview-1, and the warning payload is delivered separately.Observed result after fix: the finalized preview remains attached while the tool-warning final is sent through normal Discord delivery.
What was not tested: live Discord guild transport with Pi runtime credentials was not run here.
Related before-fix log: with only the production guard removed, the same targeted test fails at
expect(draftStream.clear).not.toHaveBeenCalled()becausedraftStream.clear()is called once.Verification
git diff --checknode scripts/run-vitest.mjs extensions/discord/src/monitor/message-handler.process.test.tsnode scripts/run-vitest.mjs extensions/discord/src/monitor/message-handler.process.test.ts src/channels/message/lifecycle.test.tspnpm check:changedOut of scope
Made with Cursor