Skip to content

fix(discord): avoid duplicate typing keepalive for tool replies#84288

Open
dr00-eth wants to merge 1 commit into
openclaw:mainfrom
dr00-eth:fix/message-tool-typing-lifecycle
Open

fix(discord): avoid duplicate typing keepalive for tool replies#84288
dr00-eth wants to merge 1 commit into
openclaw:mainfrom
dr00-eth:fix/message-tool-typing-lifecycle

Conversation

@dr00-eth

@dr00-eth dr00-eth commented May 19, 2026

Copy link
Copy Markdown

Summary

  • Problem: message-tool-only Discord replies could refresh typing from two lifecycle owners, making Discord show stale multi-second "bot is typing" after the visible reply was already delivered.
  • Solution: make the core typing keepalive an explicit per-run option, have Discord opt out only for unconfigured message_tool_only runs, and disable the Discord plugin's nested callback keepalive.
  • What changed: added typingKeepalive?: boolean to reply options, kept shared reply typing periodic by default, pass typingKeepalive: false from Discord only when sourceReplyDeliveryMode === "message_tool_only" and no explicit typingMode is configured, and set the Discord channel callback keepalive interval to 0.
  • What did NOT change (scope boundary): explicit agents.defaults.typingMode / session.typingMode continues to win, non-Discord channels keep the existing shared keepalive default unless they opt out, typing is not disabled by default, ack reactions are unchanged, and this does not add a Discord "stop typing" call because Discord does not expose one.

Motivation

  • This tightens the user-visible lifecycle after the duplicate-reply regression was fixed. The run itself was clean, but Discord could continue to show typing because OpenClaw refreshed typing near or after the visible message tool delivery. That made it look as if a second response was still being generated even when the transcript showed no fallback or second assistant run.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: stale Discord typing after a successful message-tool-only visible reply.
  • Real environment tested: macOS local OpenClaw v2026.5.18 gateway, Node v25.6.1, Discord channel #founders-club (channel id ending 5258), Pi runtime, openai/gpt-5.5, messages.groupChat.visibleReplies: "message_tool".
  • Exact steps or command run after this patch:
    1. Applied the typing lifecycle patch locally to the installed OpenClaw runtime.
    2. Restarted the gateway.
    3. Sent Discord test messages in #founders-club.
    4. Checked the transcript and gateway log for second runs/fallbacks and observed Discord typing behavior.
  • Evidence after fix (redacted runtime facts):
    • User message id 1506376771032056000.
    • One gpt-5.5 tool call at 2026-05-19T19:23:44.803Z.
    • One delivery mirror at 2026-05-19T19:23:45.823Z.
    • One successful message tool result at 2026-05-19T19:23:45.982Z with primary Discord message id 1506376839398952981.
    • Gateway log after delivery only showed context maintenance / normal bookkeeping; there was no second assistant run or fallback.
  • Observed result after fix: typing is sent for the actual run, but OpenClaw no longer owns two periodic keepalive loops for the same Discord reply path. Residual short Discord-client typing visibility can still happen after the last typing pulse because Discord has no explicit stop-typing endpoint.
  • What was not tested: full pnpm build && pnpm check && pnpm test; I ran focused validation for the touched auto-reply and Discord extension surfaces.
  • Before evidence: after the duplicate-reply fix, live Discord still showed "DrewBot is typing" after the visible reply even when logs showed no second assistant/fallback. That pointed at typing lifecycle refresh, not duplicate generation.

Root Cause (if applicable)

  • Root cause: default message-tool-only group replies start typing immediately, and Discord typing was being refreshed by both core reply typing and a Discord channel callback keepalive. For a path where the visible reply is delivered by the message tool, a late refresh can outlive the actual response and appear as phantom typing.
  • Missing detection / guardrail: tests covered typing start/cleanup behavior generally, but not "send the first typing signal without a periodic keepalive" or "Discord channel callbacks should not start a nested typing keepalive."
  • Contributing context (if known): Discord typing indicators are TTL-based and cannot be explicitly stopped by the bot API.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/auto-reply/reply/reply-utils.test.ts
    • extensions/discord/src/monitor/message-handler.process.test.ts
  • Scenario the test should lock in: a typing controller can emit the first typing signal without periodic keepalive ticks; Discord processing does not start a nested callback keepalive; configured Discord typingMode preserves the core keepalive path.
  • Why this is the smallest reliable guardrail: it verifies the exact lifecycle knobs that caused stale typing without relying on Discord client TTL timing.
  • Existing test that already covers this (if any): none.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • For Discord message-tool-only source replies with no explicit typingMode, OpenClaw still sends the initial typing cue but does not periodically refresh it. Explicitly configured typing behavior and non-Discord shared defaults are preserved.

Diagram (if applicable)

Before:
Discord source reply run -> core typing keepalive
                         -> Discord callback keepalive
                         -> visible message delivered -> typing may still be refreshed

After:
Discord source reply run -> core one-shot typing only for unconfigured message_tool_only
                         -> visible message delivered -> no OpenClaw periodic refresh by default

Security Impact (required)

  • New permissions/capabilities? (Yes/No): No
  • Secrets/tokens handling changed? (Yes/No): No
  • New/changed network calls? (Yes/No): No
  • Command/tool execution surface changed? (Yes/No): No
  • Data access scope changed? (Yes/No): No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local OpenClaw gateway, Node v25.6.1
  • Model/provider: openai/gpt-5.5, Pi runtime
  • Integration/channel (if any): Discord group/channel, message-tool-only visible replies
  • Relevant config (redacted): messages.groupChat.visibleReplies: "message_tool", no explicit typingMode

Steps

  1. Configure a Discord group/channel room for message-tool-only visible replies.
  2. Trigger the agent in a group channel.
  3. Have the assistant send the visible source reply via message(action=send).
  4. Observe Discord typing and inspect logs/transcript for whether a second run exists.

Expected

  • Typing starts for the real run.
  • Visible reply sends once.
  • OpenClaw does not continue periodically refreshing Discord typing after the reply path completes unless typing mode was explicitly configured.

Actual

  • Before this patch: stale typing could persist after the visible reply because multiple keepalive paths refreshed the indicator.
  • After this patch: no duplicate keepalive path is active; live transcript/logs show one tool call, one delivery, one successful result, and no second run.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Focused validation run on this branch:

git diff --check origin/main...HEAD
node scripts/run-vitest.mjs run --config test/vitest/vitest.auto-reply-reply.config.ts src/auto-reply/reply/reply-utils.test.ts src/auto-reply/reply/typing-persistence.test.ts
  Test Files  2 passed (2)
  Tests       73 passed (73)

node scripts/run-vitest.mjs run --config test/vitest/vitest.extension-discord.config.ts extensions/discord/src/monitor/message-handler.process.test.ts
  Test Files  1 passed (1)
  Tests       71 passed (71)

Human Verification (required)

  • Verified scenarios: live Discord message-tool-only reply after local runtime patch; transcript/logs showed one assistant tool call, one delivery mirror, one successful tool result, and no second assistant/fallback.
  • Edge cases checked: explicit typing mode still preserves configured behavior; the one-shot behavior applies only to Discord when sourceReplyDeliveryMode === "message_tool_only" and typing mode is not configured.
  • What you did not verify: full repository test suite, exact Discord client TTL duration after the final typing pulse.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No): Yes
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Discord users who relied on unconfigured message-tool-only typing refreshing throughout very long runs may see fewer typing refreshes.
    • Mitigation: explicit agents.defaults.typingMode or session.typingMode continues to opt into configured behavior; non-Discord channels keep their existing shared keepalive default unless they explicitly opt out.

@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Workflow note: Future ClawSweeper reviews update this same comment in place.

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.

Summary
The PR adds a per-run typing keepalive opt-out, uses it for unconfigured Discord message-tool-only replies, disables Discord's nested callback keepalive, and adds focused lifecycle tests.

Reproducibility: yes. Source inspection shows current main wires both the shared typing controller and Discord callback keepalive, and the PR body supplies live Discord after-fix log evidence; I did not run live Discord in this read-only review.

PR rating
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Summary: Good focused PR with sufficient live log proof and no blocking findings, with one maintainer-facing compatibility decision remaining.

Rank-up moves:

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.

PR egg
✨ Hatched: 🥚 common Mossy Test Hopper

       _..------.._          
    .-'  .-.  .-.  '-.       
   /    ( * )( * )    \      
  |        .--.        |     
  |   <\   ====   />   |     
   \    '.______.'    /      
    '-._   ____   _.-'       
        `-.____.-'           
       __/|_||_|\__          
      /__.'    '.__\         
       .-----------.         
      '-------------'        

Rarity: 🥚 common.
Trait: polishes edge cases.
Image traits: location release reef; accessory tiny test log scroll; palette cobalt, lime, and pearl; mood calm; pose balancing on a branch marker; shell frosted glass shell; lighting soft underwater shimmer; background smooth stones and checkmarks.
Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Mossy Test Hopper in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • How to hatch it: reach status: 👀 ready for maintainer look or status: 🚀 automerge armed; that usually means sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness.
  • When the egg is hatchable, the PR author or a maintainer can comment @clawsweeper hatch to generate its image.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

Real behavior proof
Sufficient (logs): The PR body provides a real Discord environment, exact steps, redacted runtime log facts, and the observed after-fix result for the changed message-tool-only path.

Mantis proof suggestion
A short Discord UI recording would materially show the user-visible typing indicator behavior after a message-tool-only reply. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

visual task: record a Discord message_tool_only reply and verify typing appears during the run but does not keep refreshing after the visible message is delivered.

Risk before merge
Why this matters: - Unconfigured Discord message-tool-only rooms that relied on periodic typing refreshes throughout long tool runs will now get an initial cue only; explicit typingMode remains the opt-in path for the old refresh behavior.

Maintainer options:

  1. Accept the scoped Discord one-shot default (recommended)
    Land this patch if maintainers agree that unconfigured Discord message-tool-only runs should send only the initial typing cue while explicit typingMode restores refreshes.
  2. Require opt-in or documentation first
    Ask for a small follow-up if maintainers want the old long-run refresh behavior preserved by default or want the Discord-specific UX change documented before merge.
  3. Pause for the broader lifecycle PR
    Pause or close this PR if Fix Discord reply typing lifecycle #76091 is chosen as the canonical owner for Discord typing lifecycle changes.

Next step before merge
No automated repair is needed; this is ready for maintainer merge judgment on the scoped compatibility tradeoff and overlap with the related Discord lifecycle PR.

Security
Cleared: The diff only changes typing lifecycle plumbing and focused tests; it does not add permissions, dependencies, secrets handling, or new execution surfaces.

Review details

Best possible solution:

Land the scoped Discord fix if maintainers accept the message-tool-only typing UX tradeoff and coordinate merge order with the broader Discord typing lifecycle PR.

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

Yes. Source inspection shows current main wires both the shared typing controller and Discord callback keepalive, and the PR body supplies live Discord after-fix log evidence; I did not run live Discord in this read-only review.

Is this the best way to solve the issue?

Yes, with maintainer acceptance of the UX tradeoff. The follow-up scopes one-shot typing to Discord message-tool-only runs without explicit typingMode while preserving shared defaults, which is the narrow maintainable fix path.

Label justifications:

  • P2: This is a normal-priority Discord UX bug fix with a limited surface and focused regression coverage.
  • merge-risk: 🚨 compatibility: The PR intentionally changes default typing refresh behavior for existing unconfigured Discord message-tool-only rooms.

What I checked:

  • Current main has both core and Discord callback typing ownership: Current main builds Discord channel typing callbacks for the reply pipeline while the shared reply controller separately owns run typing, which matches the duplicate keepalive root cause described by the PR. (extensions/discord/src/monitor/message-handler.process.ts:412, 3d96111a5afe)
  • Current shared typing controller starts a periodic loop by default: The current shared controller refreshes TTL and starts the keepalive loop from startTypingLoop, so a caller-level opt-out is a direct fix point for one-shot typing. (src/auto-reply/reply/typing.ts:178, 3d96111a5afe)
  • PR diff scopes the behavior change: The PR head adds typingKeepalive, passes false only for Discord message_tool_only runs with no configured typing mode, sets the Discord callback keepalive interval to 0, and adds tests for nested keepalive suppression and configured typing preservation. (extensions/discord/src/monitor/message-handler.process.ts:220, 3439ab10b616)
  • Configured typing mode contract exists: Docs and source describe agents.defaults.typingMode/session.typingMode plus typingIntervalSeconds as the supported way to control typing start and refresh cadence, so preserving explicit config is the right compatibility boundary. Public docs: docs/concepts/typing-indicators.md. (docs/concepts/typing-indicators.md:8, 3d96111a5afe)
  • Focused proof supplied in PR body: The PR body reports a real Discord message-tool-only run after the patch with one tool call, one delivery mirror, one successful message result, and no second assistant run or fallback. (3439ab10b616)
  • Related lifecycle work remains open: The provided GitHub context links Fix Discord reply typing lifecycle #76091 as an open PR touching Discord reply typing lifecycle ownership, so merge order should be coordinated even though this patch is narrower. (8ef178db08f1)

Likely related people:

  • joshavant: Current main blame ties the central Discord process path and shared typing controller to commit e996159, which shaped the behavior this PR adjusts. (role: recent area contributor; confidence: high; commits: e99615973810; files: extensions/discord/src/monitor/message-handler.process.ts, src/auto-reply/reply/typing.ts)
  • zhuisDEV: The related open PR Fix Discord reply typing lifecycle #76091 is focused on Discord reply typing lifecycle ownership and overlaps the same review surface. (role: adjacent contributor; confidence: medium; commits: 8ef178db08f1; files: extensions/discord/src/monitor/message-handler.process.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 3d96111a5afe.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels May 19, 2026
@dr00-eth dr00-eth force-pushed the fix/message-tool-typing-lifecycle branch from 5e8971f to 3439ab1 Compare May 19, 2026 20:26
@dr00-eth

Copy link
Copy Markdown
Author

@clawsweeper re-review

Follow-up for the compatibility/rank-up note is pushed in 3439ab10b6.

What changed:

  • Shared reply typing now keeps its existing periodic keepalive default unless a caller explicitly sets typingKeepalive: false.
  • Discord sets typingKeepalive: false only for unconfigured message_tool_only runs, so the stale post-delivery typing fix is Discord-scoped.
  • Configured Discord typingMode now preserves the core keepalive path, with regression coverage.

Validation:

  • git diff --check origin/main...HEAD
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.auto-reply-reply.config.ts src/auto-reply/reply/reply-utils.test.ts src/auto-reply/reply/typing-persistence.test.ts -> 2 files, 73 tests passed
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.extension-discord.config.ts extensions/discord/src/monitor/message-handler.process.test.ts -> 1 file, 71 tests passed

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant