Summary
The Slack assistant loading indicator briefly disappears and reappears during long-running turns, ~1–2 minutes after the turn starts. The flash coincides with the first reportProgress call following a long tool-call gap.
Root cause
status-scheduler.ts replaces loading_messages with a single-item array on every progress update:
// status-scheduler.ts
const getLoadingMessagesForVisibleStatus = (visible) =>
visible ? [visible] : undefined; // always 1 item
On status.start(), Slack receives the full 10-item shuffled carousel ("Consulting the orb", "Bribing the gremlins", …). When reportProgress fires and status.update() posts loading_messages: ["Searching docs"], Slack tears down the 10-item rotation and resets to a single message — the reset is visible as a flash.
This diverges from the canonical Slack pattern: every official sample (bolt-js-assistant-template, bolt-js-support-agent) calls setStatus once at turn start with a fixed loading_messages list and never updates it mid-turn. Progress is shown via streaming, not repeated setStatus calls.
Confidence: high — reproduced via code trace; timing matches reported 1–2 minute onset (first reportProgress after a long tool call).
Secondary issues (same investigation)
reply-executor.ts finally block calls status.stop() unconditionally — including after return from the timeout-resume catch path — explicitly clearing the indicator before the new invocation re-establishes it.
slack-resume.ts calls await status.stop() immediately before postSlackApiReplyPosts(). Slack auto-clears the indicator when a reply posts; the explicit stop creates an unnecessary pre-reply gap.
Short-term fix
Stop replacing loading_messages on mid-turn updates. The update() path should leave loading_messages unchanged (keep the carousel from start()) and skip the setStatus call when only the visible text key has changed and the array is already stable. Fix the unconditional status.stop() in the finally block for the timeout-resume path with a preserveStatusForResume flag.
Long-term proposal
Migrate from repeated setStatus updates to Slack's native chatStream with task_update chunks — the Cursor-like inline progress widget. @chat-adapter/slack already supports this via adapter.stream(threadId, asyncIterable, { taskDisplayMode: 'timeline' }). Under this model:
setStatus is called once at turn start (no mid-turn updates, no carousel reset)
reportProgress calls become task_update chunks (pending → in_progress → complete) appearing inline in the reply message
- The final reply streams via
markdown_text chunks; the streamer stop() auto-clears the status
This matches the platform's intended pattern and removes the loading_messages flicker entirely.
Action taken on behalf of David Cramer.
Summary
The Slack assistant loading indicator briefly disappears and reappears during long-running turns, ~1–2 minutes after the turn starts. The flash coincides with the first
reportProgresscall following a long tool-call gap.Root cause
status-scheduler.tsreplacesloading_messageswith a single-item array on every progress update:On
status.start(), Slack receives the full 10-item shuffled carousel ("Consulting the orb", "Bribing the gremlins", …). WhenreportProgressfires andstatus.update()postsloading_messages: ["Searching docs"], Slack tears down the 10-item rotation and resets to a single message — the reset is visible as a flash.This diverges from the canonical Slack pattern: every official sample (
bolt-js-assistant-template,bolt-js-support-agent) callssetStatusonce at turn start with a fixedloading_messageslist and never updates it mid-turn. Progress is shown via streaming, not repeatedsetStatuscalls.Confidence: high — reproduced via code trace; timing matches reported 1–2 minute onset (first
reportProgressafter a long tool call).Secondary issues (same investigation)
reply-executor.tsfinallyblock callsstatus.stop()unconditionally — including afterreturnfrom the timeout-resume catch path — explicitly clearing the indicator before the new invocation re-establishes it.slack-resume.tscallsawait status.stop()immediately beforepostSlackApiReplyPosts(). Slack auto-clears the indicator when a reply posts; the explicit stop creates an unnecessary pre-reply gap.Short-term fix
Stop replacing
loading_messageson mid-turn updates. Theupdate()path should leaveloading_messagesunchanged (keep the carousel fromstart()) and skip thesetStatuscall when only the visible text key has changed and the array is already stable. Fix the unconditionalstatus.stop()in thefinallyblock for the timeout-resume path with apreserveStatusForResumeflag.Long-term proposal
Migrate from repeated
setStatusupdates to Slack's nativechatStreamwithtask_updatechunks — the Cursor-like inline progress widget.@chat-adapter/slackalready supports this viaadapter.stream(threadId, asyncIterable, { taskDisplayMode: 'timeline' }). Under this model:setStatusis called once at turn start (no mid-turn updates, no carousel reset)reportProgresscalls becometask_updatechunks (pending → in_progress → complete) appearing inline in the reply messagemarkdown_textchunks; the streamerstop()auto-clears the statusThis matches the platform's intended pattern and removes the
loading_messagesflicker entirely.Action taken on behalf of David Cramer.