Skip to content

Slack draft cleanup can delete visible final replies after late same-turn payloads #87363

@tianxiaochannel-oss88

Description

@tianxiaochannel-oss88

Summary

Slack draft-preview cleanup can still run after a final reply is already visible in the Slack thread. If a late same-turn payload arrives afterward, for example a tool/recovery error after the assistant already produced a final answer, the late fallback path can reuse the draft finalizer and call draftStream.clear(). In Slack, clear() deletes the draft message, so a user-visible answer can disappear or be replaced by a later cleanup/error payload.

This is adjacent to, but narrower than, #83165. That issue is about durable delivery queue recovery and partial media failure. This report is about in-turn Slack draft cleanup after final-visible delivery.

Observed evidence

From a local Slack Socket Mode run, with gateway and Slack connectivity otherwise healthy:

  • OpenClaw logged a Slack final delivery around 2026-05-28 00:45:22 +08:00.
  • The Slack session transcript then recorded a Slack message deletion about 15 seconds later.
  • Around the same turn, the model/recovery layer logged Responses continuation recovery activity and later error/retry handling.
  • The user saw a reply appear in Slack, then later disappear / get replaced by a tool or recovery failure message.

Sensitive details are intentionally omitted: no token, account, full channel id, user id, raw session id, or full Responses item id is included here.

Source-level cause

The Slack dispatch path already protects drafts after an in-place preview finalization by setting draftPreviewCommitted. However, a visible final reply can also be delivered through normal delivery paths. After that happens, later same-turn payloads can still receive a live draft adapter when draftPreviewCommitted is false.

The relevant behavior is:

  • extensions/slack/src/draft-stream.ts: clear() calls Slack message delete for the draft message.
  • extensions/slack/src/monitor/message-handler/dispatch.ts: late final/error payloads can still pass the draft into the finalizable live preview adapter unless the draft was finalized in place.
  • src/channels/message/live.ts: fallback normal delivery cleans up the draft after delivery by calling draft.clear().

That fallback cleanup is correct for the first replacement of a transient draft preview, but it should not be reused after the same turn has already produced a visible final reply.

Expected behavior

Once a final reply is visibly delivered for a Slack turn, that turn's final content should be terminal for draft cleanup purposes:

  • Later same-turn tool/recovery/error payloads may be delivered as separate visible messages if policy allows.
  • They must not reuse the draft finalizer in a way that calls draftStream.clear() on an already visible final/draft message.
  • Existing first-fallback behavior for non-finalized previews should remain unchanged, so transient previews can still be cleaned up when replaced by the initial normal final delivery.

Proposed fix shape

Track that a visible final reply has already been delivered in Slack dispatch, regardless of whether it was delivered by preview finalization, normal delivery, or native stream delivery. After that flag is set, do not pass the draft stream into later finalizable-preview handling for the same turn.

This keeps the generic live-preview lifecycle unchanged and makes the Slack-specific deletion guard explicit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions