Skip to content

fix(discord): apply tool status emojis immediately to avoid override by thinking reactions#93488

Merged
vincentkoc merged 4 commits into
openclaw:mainfrom
lzyyzznl:fix/discord-status-reactions-immediate-92715
Jun 16, 2026
Merged

fix(discord): apply tool status emojis immediately to avoid override by thinking reactions#93488
vincentkoc merged 4 commits into
openclaw:mainfrom
lzyyzznl:fix/discord-status-reactions-immediate-92715

Conversation

@lzyyzznl

Copy link
Copy Markdown
Contributor

Summary

  • Discord intermediate status reaction emojis (🛠️, 🌐, 🔎, etc.) during tool/skill execution were not appearing on messages
  • Only initial (👀), stall warnings (⏳, ⚠️), thinking (🧠), and final (✅/❌) emojis were visible
  • Root cause: setTool() used a debounced (700ms) emoji schedule shared with setThinking(), so rapid onReasoningStream callbacks would repeatedly overwrite the pending tool emoji with 🧠 before it could be applied
  • Fix: apply tool emojis immediately (skip debounce) — tool transitions are user-facing state changes that should be visible without delay

Fixes #92715

Linked context

  • Issue [Bug]: Discord intermediate status reaction emojis missing during tool/skill execution #92715 reports that intermediate tool/skill emojis don't appear on Discord messages during tool execution
  • The Discord message handler wires onToolStartstatusReactions.setTool() and onReasoningStreamstatusReactions.setThinking() (message-handler.process.ts:1013-1024)
  • When a tool starts, the agent may continue generating reasoning tokens, causing interleaved setTool() and setThinking() calls
  • Both functions shared the same 700ms debounce timer via scheduleEmoji(), so the last call (typically setThinking()) would win

Real behavior proof

Behavior addressed: Tool status reaction emojis now appear immediately on Discord messages instead of being silently dropped

Real setup tested:

  • Runtime: node
  • Test framework: vitest v4.1.7
  • Module: src/channels/status-reactions.ts — the controller is platform-agnostic; the fix applies to all channels using createStatusReactionController

Exact steps or command run after fix:

cd openclaw
pnpm test -- --run src/channels/status-reactions.test.ts

After-fix evidence:

$ pnpm test -- --run src/channels/status-reactions.test.ts

 RUN  v4.1.7 /home/0668001315/Desktop/LCAP-300/openclaw-issue/openclaw

 Test Files  1 passed (1)
      Tests  43 passed (43)
   Start at  12:20:25
   Duration  1.21s (transform 658ms, setup 562ms, import 119ms, tests 176ms, environment 0ms)

The key behavioral test "should apply tool emojis immediately (not debounced)" validates:

// Before fix (debounced): only 🧠 would appear; 🔎 and 🛠️ never reach Discord
// After fix (immediate):  🔎 applied immediately, then 🛠️ immediately
void controller.setThinking();
await vi.advanceTimersByTimeAsync(100);

void controller.setTool("web_search");    // → 🔎 applied immediately
await vi.advanceTimersByTimeAsync(100);

void controller.setTool("exec");          // → 🛠️ applied immediately
await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

const setEmojis = collectEmojisForMethod(calls, "set");
expect(setEmojis).toEqual(["🔎", "🛠️"]);  // Both tool emojis now visible

Observed result after the fix: All 43 tests pass. The tool emoji sequence test now confirms that each distinct tool emoji is applied immediately to Discord, not debounced away by interleaved setThinking() calls.

What was not tested: End-to-end test with a live Discord bot token (requires credentials). The controller behavior is fully covered by unit tests. The Discord message handler integration (message-handler.process.ts) has 104 additional tests that continue to pass.

Proof limitations or environment constraints: The emoji reaction behavior is inherently a UI concern that requires Discord API credentials to demonstrate end-to-end. The unit tests validate the controller's scheduling behavior at the call level, which is the correct abstraction boundary for this fix.

Tests and validation

  • pnpm test -- --run src/channels/status-reactions.test.ts — 43/43 passed
  • pnpm test -- --run extensions/discord/src/monitor/message-handler.process.test.ts — 104/104 passed
  • Previously existing test "should only fire last state when rapidly changing" was renamed/updated to "should apply tool emojis immediately (not debounced)" and now asserts the corrected behavior: tool emojis are applied immediately rather than debounced

Risk checklist

Did user-visible behavior change? (Yes)

  • Tool emojis now appear during execution instead of being missing; this is the intended fix

Did config, environment, or migration behavior change? (No)

Did security, auth, secrets, network, or tool execution behavior change? (No)

What is the highest-risk area?

  • The setTool() change from debounced to immediate increases the number of setReaction() API calls per tool invocation. On Discord, rate limits apply at 5 requests per 5 seconds per channel. For tools that execute very rapidly (<200ms apart), this could theoretically approach rate limits.

How is that risk mitigated?

  • The controller's applyEmoji already skips duplicate emoji sends (same emoji => no-op)
  • The promise-chain serialization (enqueue()) prevents concurrent API calls
  • Terminal states (done/error) flush and cease all further calls
  • The before-fix behavior silently dropped EVERY tool emoji, which is worse UX than the small chance of rate limiting under extreme conditions

Current review state

What is the next action?

  • Maintainer review

What is still waiting on author, maintainer, CI, or external proof?

  • CI checks
  • End-to-end verification with a live Discord bot (maintainer can run with credentials)

Which bot or reviewer comments were addressed?

  • N/A (new PR)

@openclaw-barnacle openclaw-barnacle Bot added size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 16, 2026
@clawsweeper

clawsweeper Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 16, 2026, 12:49 AM ET / 04:49 UTC.

Summary
The PR changes the shared status-reaction controller so setTool schedules tool emojis immediately and updates the controller unit test expected sequence.

PR surface: Source +4, Tests +1. Total +5 across 2 files.

Reproducibility: yes. at source level: current main shows the controller debounce can drop intermediate tool emojis, and the Discord default path can still filter onToolStart before setTool. I did not run a live Discord reproduction in this read-only review.

Review metrics: 2 noteworthy metrics.

  • Overlapping Fix Branches: 3 open PRs. Three open PRs target the same Discord status-reaction bug, so maintainers should select one canonical path before merge.
  • Exported SDK Behavior: 1 method timing changed. createStatusReactionController is exported via openclaw/plugin-sdk/channel-feedback, so immediate setTool semantics affect all bundled and external consumers.

Root-cause cluster
Relationship: partial_overlap
Canonical: #92715
Summary: The canonical remaining problem is the Discord intermediate status-reaction bug; this PR addresses controller timing, while sibling PRs address dispatch and follow-up callback gates.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🦪 silver shellfish
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body supplies unit-test output only and explicitly lacks a live Discord run; add redacted Discord proof such as a recording, reaction timeline, logs, terminal/live output, or Mantis artifact, then update the PR body to trigger re-review or ask a maintainer for @clawsweeper re-review.

Mantis proof suggestion
A real Discord status-reaction recording would directly prove the visible transport behavior and distinguish controller timing from callback-gating issues. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis discord status reactions: verify a Discord multi-tool request shows intermediate tool reactions with verbose/tool summaries off before the final done reaction.

Risk before merge

  • [P1] Merging this PR as-is would likely close [Bug]: Discord intermediate status reaction emojis missing during tool/skill execution #92715 while the default Discord path can still suppress onToolStart before the controller-level change runs.
  • [P1] The PR changes timing behavior on an exported plugin SDK status-reaction controller, which can increase reaction API calls or alter lifecycle timing for all consumers without real transport proof.
  • [P1] There are three open PRs targeting the same issue, so maintainers need one canonical or combined path rather than competing partial fixes.

Maintainer options:

  1. Consolidate The Full Status-Reaction Fix (recommended)
    Choose or combine the branch that covers the callback gates and any needed controller timing change, then require real Discord proof before merge.
  2. Accept Controller Timing As A Partial Step
    Maintainers may intentionally land the shared-controller timing change, but it should not close the linked Discord bug until the callback-gating path is also fixed and proven.
  3. Pause Competing Partial Branches
    If a sibling branch becomes the proven canonical fix, pause or close the remaining partial PRs instead of keeping multiple open landing candidates.

Next step before merge

  • [P1] Maintainers need to choose or combine the overlapping fix branches and require live Discord proof; automation should not repair this PR while contributor real-behavior proof is missing.

Security
Cleared: The diff is limited to TypeScript status-reaction timing and tests, with no dependency, workflow, permission, secret, package, or supply-chain surface changes.

Review findings

  • [P2] Fix the callback gate before claiming the Discord bug — src/channels/status-reactions.ts:369
Review details

Best possible solution:

Land one canonical narrow fix that forwards Discord status-reaction lifecycle updates through the dispatch and follow-up gates without re-enabling verbose text progress, then prove it in real Discord; keep the immediate setTool change only if shared-controller tests show it is needed.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main shows the controller debounce can drop intermediate tool emojis, and the Discord default path can still filter onToolStart before setTool. I did not run a live Discord reproduction in this read-only review.

Is this the best way to solve the issue?

No. A controller-only debounce change is not the best complete fix for the linked Discord bug; the maintainable fix must also decouple lifecycle status reactions from visible tool-summary callback gates and then prove the transport behavior.

Full review comments:

  • [P2] Fix the callback gate before claiming the Discord bug — src/channels/status-reactions.ts:369
    This only changes behavior after onToolStart reaches the shared controller. In the linked default Discord path, current main still wraps onToolStart with requiresToolSummaryVisibility: true, and verboseDefault defaults off, so the callback can be dropped before message-handler.process.ts calls statusReactions.setTool. Merging this with the current closing reference would close [Bug]: Discord intermediate status reaction emojis missing during tool/skill execution #92715 while default users can still miss tool-phase reactions; the fix needs to forward lifecycle status callbacks independently of visible tool summaries, then keep this timing change only if it is still needed.
    Confidence: 0.91

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 88c9e4d644e1.

Label changes

Label changes:

  • add P2: This is a normal user-visible Discord status feedback bugfix with limited channel UX blast radius.
  • add merge-risk: 🚨 compatibility: The diff changes timing behavior on an exported plugin SDK controller and needs shared-channel proof before merge.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body supplies unit-test output only and explicitly lacks a live Discord run; add redacted Discord proof such as a recording, reaction timeline, logs, terminal/live output, or Mantis artifact, then update the PR body to trigger re-review or ask a maintainer for @clawsweeper re-review.

Label justifications:

  • P2: This is a normal user-visible Discord status feedback bugfix with limited channel UX blast radius.
  • merge-risk: 🚨 compatibility: The diff changes timing behavior on an exported plugin SDK controller and needs shared-channel proof before merge.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body supplies unit-test output only and explicitly lacks a live Discord run; add redacted Discord proof such as a recording, reaction timeline, logs, terminal/live output, or Mantis artifact, then update the PR body to trigger re-review or ask a maintainer for @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +4, Tests +1. Total +5 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 5 1 +4
Tests 1 4 3 +1
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 9 4 +5

What I checked:

  • PR diff: The branch changes only setTool in the shared controller from debounced scheduling to scheduleEmoji(emoji, { immediate: true }) and adjusts one unit test expectation. (src/channels/status-reactions.ts:363, d8d71c88eb34)
  • Current controller behavior: On current main, setThinking and setTool both use the same debounced scheduleEmoji path, so the controller-level overwrite described by this PR is source-plausible when both callbacks reach the controller. (src/channels/status-reactions.ts:359, 88c9e4d644e1)
  • Discord callback target: Discord wires onReasoningStream to statusReactions.setThinking() and onToolStart to statusReactions.setTool(payload.name), so forwarding tool-start callbacks is required before this controller change can affect Discord users. (extensions/discord/src/monitor/message-handler.process.ts:1013, 88c9e4d644e1)
  • Remaining default-path gate: Current main still wraps onToolStart with requiresToolSummaryVisibility: true; shouldForwardProgressCallback returns false when tool summaries are off unless the quiet channel-owned exception applies, so a controller-only change does not prove the default Discord bug is fixed. (src/auto-reply/reply/dispatch-from-config.ts:2755, 88c9e4d644e1)
  • Documented default and contract: Docs say verboseDefault defaults to off, while status reactions are lifecycle feedback on Discord when ack reactions are active; that makes a verbose/tool-summary gate relevant to the reporter's default-config symptom. Public docs: docs/gateway/config-agents.md. (docs/gateway/config-agents.md:447, 88c9e4d644e1)
  • Linked issue evidence: The linked issue reports OpenClaw v2026.6.6 on Discord showing initial, stall, and final reactions but no tool-phase reactions during a multi-tool request; a prior ClawSweeper review identified the callback-gating path as the likely source-level blocker.

Likely related people:

  • Peter Steinberger: Recent shared status-reaction cleanup touched the central controller and adjacent tests used by this PR. (role: recent area contributor; confidence: medium; commits: 4373103c2252; files: src/channels/status-reactions.ts, src/channels/status-reactions.test.ts, src/channels/status-reactions.slack-lifecycle.test.ts)
  • Shadow: Prior status-reaction timer and gating work is directly adjacent to this debounce/timing behavior. (role: adjacent status-reaction contributor; confidence: medium; commits: 30a0d3fce18c; files: src/channels/status-reactions.ts, src/channels/status-reactions.test.ts)
  • Hsiao A: The shared status-reaction lifecycle for tool/thinking progress indicators appears in the Slack/status reaction lifecycle feature commit. (role: introduced shared lifecycle behavior; confidence: medium; commits: cea7162490f4; files: src/channels/status-reactions.ts, src/channels/status-reactions.slack-lifecycle.test.ts)
  • scoootscooob: The Discord message-handler path involved in this issue was moved into extensions/discord by this refactor commit. (role: Discord channel refactor contributor; confidence: low; commits: 5682ec37fada; files: extensions/discord/src/monitor/message-handler.process.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 16, 2026
@vincentkoc vincentkoc self-assigned this Jun 16, 2026
lzyyzznl and others added 4 commits June 16, 2026 13:33
…by thinking reactions

Tool emoji reactions (🛠️, 🌐, 🔎, etc.) during Discord tool/skill execution
were not appearing because setTool() used a 700ms debounce shared with
setThinking(). Rapid onReasoningStream calls from overlapping reasoning
would repeatedly overwrite the pending tool emoji with 🧠, so the tool
emoji never reached Discord.

Fix by making setTool() apply emojis immediately (skip debounce). Tool
transitions are user-facing state changes that should be visible without
delay, and the terminal done/error transitions already flush any pending
state.

Fixes openclaw#92715.
@vincentkoc
vincentkoc force-pushed the fix/discord-status-reactions-immediate-92715 branch from d8d71c8 to 11bf896 Compare June 16, 2026 05:33
@vincentkoc

Copy link
Copy Markdown
Member

Land-ready maintainer repair pushed to the contributor branch.

Review repair moved the fix to the shared dispatch/follow-up boundary: Discord and Slack explicitly opt into channel-owned tool lifecycle status while verbose tool text remains hidden. Slack tool-only source policy now also permits the opted-in status callback; other item/command progress stays suppressed.

Verification:

  • four changed-path focused tests passed: dispatch, queued follow-up, Discord reaction lifecycle, and Slack tool-only status reactions
  • broader dispatch file: 209/210 passed; one unrelated pre-existing CLI-owned-final test timed out after 120s
  • git diff --check origin/main...HEAD: clean
  • final branch autoreview: clean, no accepted/actionable findings
  • rebased onto latest origin/main before push

@vincentkoc
vincentkoc merged commit 5afddf5 into openclaw:main Jun 16, 2026
22 of 23 checks passed
@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord channel: slack Channel integration: slack size: S and removed size: XS labels Jun 16, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 16, 2026
…by thinking reactions (openclaw#93488)

* fix(discord): apply tool status emojis immediately to avoid override by thinking reactions

Tool emoji reactions (🛠️, 🌐, 🔎, etc.) during Discord tool/skill execution
were not appearing because setTool() used a 700ms debounce shared with
setThinking(). Rapid onReasoningStream calls from overlapping reasoning
would repeatedly overwrite the pending tool emoji with 🧠, so the tool
emoji never reached Discord.

Fix by making setTool() apply emojis immediately (skip debounce). Tool
transitions are user-facing state changes that should be visible without
delay, and the terminal done/error transitions already flush any pending
state.

Fixes openclaw#92715.

* fix(discord): forward quiet tool lifecycle status

* fix(slack): preserve tool status reactions

* test(channels): type quiet tool lifecycle options

---------

Co-authored-by: Vincent Koc <[email protected]>
crh-code pushed a commit to crh-code/openclaw that referenced this pull request Jun 18, 2026
…by thinking reactions (openclaw#93488)

* fix(discord): apply tool status emojis immediately to avoid override by thinking reactions

Tool emoji reactions (🛠️, 🌐, 🔎, etc.) during Discord tool/skill execution
were not appearing because setTool() used a 700ms debounce shared with
setThinking(). Rapid onReasoningStream calls from overlapping reasoning
would repeatedly overwrite the pending tool emoji with 🧠, so the tool
emoji never reached Discord.

Fix by making setTool() apply emojis immediately (skip debounce). Tool
transitions are user-facing state changes that should be visible without
delay, and the terminal done/error transitions already flush any pending
state.

Fixes openclaw#92715.

* fix(discord): forward quiet tool lifecycle status

* fix(slack): preserve tool status reactions

* test(channels): type quiet tool lifecycle options

---------

Co-authored-by: Vincent Koc <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord channel: slack Channel integration: slack merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Discord intermediate status reaction emojis missing during tool/skill execution

2 participants