Skip to content

Discord: implement messages.statusReactions lifecycle (done emoji persistence) — currently Telegram-only despite docs #78431

Description

@cedricjanssens

Discord: implement messages.statusReactions lifecycle (done emoji persistence) — currently Telegram-only despite docs

Summary

The docs at docs/gateway/config-agents.md and docs/concepts/qa-e2e-automation.md state:

messages.statusReactions.enabled enables lifecycle status reactions on Slack, Discord, and Telegram.

Reading the bundled code, only Telegram actually wires the full lifecycle (queued → thinking → tool/coding/web → done/error → hold → cleanup). Discord uses only the legacy single-emoji ackReaction + cleanup-on-reply path. There is no Discord equivalent of the Telegram finalizeTelegramStatusReaction function. Setting messages.statusReactions = { enabled: true, emojis: { done: "✅" }, timing: { doneHoldMs: 3600000 } } has no observable effect on Discord guild channels — replies still cycle exactly like the bare ackReaction setup.

This is a follow-up to #78405 (silent text-reply drops) and represents the inverse problem: documented feature, missing implementation.

User-visible impact

For agents that take a while to respond (e.g. local Ollama models on big contexts: 30–120s), users have no way to distinguish "agent is still working" from "agent crashed silently". Telegram users get the full visual lifecycle (🤔 → 🔥/⚡ → ✅) and can leave a ✅ pinned long after the reply for "this turn was processed successfully" semantics.

Concrete sequence we wanted on Discord and could not get:

  1. User posts a message.
  2. Bot reacts with 🤔 immediately (works today via ackReaction).
  3. While the agent runs tool calls, the reaction transitions to 🔥 (exec) / ⚡ (web search) / 👨‍💻 (coding) — doesn't happen on Discord.
  4. When the reply is sent, the reaction transitions to ✅ — doesn't happen on Discord.
  5. ✅ stays for doneHoldMs then is cleaned up — doneHoldMs has no effect on Discord.

Currently on Discord we are forced to choose between:

  • removeAckAfterReply: false → 🤔 reappears immediately after the reply (the runtime calls restoreInitial() because there is no done transition path), so the user just sees 🤔 again, indistinguishable from "still thinking".
  • removeAckAfterReply: true → all reactions cleaned up post-reply, no visual signal that the turn completed successfully.

Neither produces "you can see at a glance which messages were processed".

Repro

OpenClaw 2026.5.5.

openclaw.json:

{
  "messages": {
    "ackReaction": "🤔",
    "ackReactionScope": "all",
    "removeAckAfterReply": false,
    "statusReactions": {
      "enabled": true,
      "emojis": { "done": "" },
      "timing": { "doneHoldMs": 3600000 }
    }
  }
}

Discord guild channel with requireMention: false, agent on local Ollama (slow inference, 30–60s reply time).

Observed: 🤔 appears immediately, no transitions during tool calls, 🤔 reappears right after the text reply is posted (no ✅ phase), doneHoldMs ignored.

Expected (per docs and Telegram parity): 🤔 → optional intermediate emojis → ✅ held for doneHoldMs → cleanup.

Code pointers (where to wire it)

The Telegram implementation is the reference. From dist/bot-D-7bCSXH.js:~4513:

const finalizeTelegramStatusReaction = async (params) => {
    if (!statusReactionController) return;
    if (params.outcome === "done") {
        await statusReactionController.setDone();
        if (removeAckAfterReply) {
            await sleepWithAbort(statusReactionTiming.doneHoldMs);
            await clearTelegramStatusReaction();
        } else {
            await statusReactionController.restoreInitial();
        }
        return;
    }
    // error path...
};

The statusReactionController factory is in dist/channel-feedback-B3QykyPj.js (finishWithEmoji, setDone, setError, etc.) — already provider-agnostic, just needs a Discord adapter (addReaction(channel, msg, emoji) / removeReaction(...)).

The Discord plugin already does single-emoji reactions (the ackReaction path), so the REST surface (PUT /channels/.../messages/.../reactions/.../@me) is wired. Plugging the existing controller into the Discord finalize callback should be small.

grep -rn "statusReactionController" dist/ returns matches only in bot-D-7bCSXH.js (Telegram). No Discord file references it, which confirms the gap.

There's also a deeper UX win: showing an in-progress emoji during long Ollama tool loops would give users a heartbeat. The same setThinking() / setTool() / setCompacting() events are already emitted by the runtime — the Discord plugin just doesn't subscribe.

Suggested implementation

  1. In extensions/discord/src/monitor/message-handler.process.ts (or wherever the Discord deliver finalize hook lives), instantiate the same createStatusReactionController factory used by Telegram, with a Discord-specific reaction adapter.
  2. Wire it into the same lifecycle event stream the Telegram path uses (pipeline.runtime-* emits the events).
  3. Add finalizeDiscordStatusReaction(params) mirroring the Telegram one — same outcome === "done" / error branches, same doneHoldMs / errorHoldMs semantics.
  4. Update docs/channels/discord.md to confirm full feature support, or — if this is intentionally Telegram-first — soften the wording in docs/gateway/config-agents.md to "Slack and Telegram only; Discord uses simplified ackReaction + cleanup-on-reply".

Why this is worth shipping

  • Doc/code mismatch is a trust-erosion issue: someone who reads the docs and configures the full lifecycle, then sees nothing, assumes their config is wrong (we lost ~1h chasing down which key was rejected by the schema).
  • Local-model usability: agents on Ollama / LM Studio can take 30–120s. Lifecycle reactions are exactly the use case where they pay off most. Cloud-model setups (GPT, Claude) reply in 2–5s and don't need it as much, which is probably why this gap survived — a Telegram-cloud user wouldn't notice.
  • Parity is a small change: the runtime emits the events, the controller factory is already abstract, the Discord REST API supports per-message reactions. Mostly wiring + an adapter.

Environment

  • OpenClaw 2026.5.5 (also tested 2026.5.4 with same result)
  • Discord: bot in a personal guild, requireMention: false, multiple bound agents (one slow Ollama-backed, others fast Lobster pipelines)
  • Workaround currently in use: statusReactions.enabled: false, ackReaction: "🤔", ackReactionScope: "all", removeAckAfterReply: true. Visual: 🤔 during processing, removed when reply lands. Acceptable but not what the docs describe.

Happy to test patches on main. Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.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:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.maturity:stableIssue affects a taxonomy feature currently scored M4/M5.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions