Skip to content

fix(discord): gate replyTo on first chunk for implicit-first replies#99083

Closed
ZOOWH wants to merge 1 commit into
openclaw:mainfrom
ZOOWH:fix/99068-discord-chunk-reply-first
Closed

fix(discord): gate replyTo on first chunk for implicit-first replies#99083
ZOOWH wants to merge 1 commit into
openclaw:mainfrom
ZOOWH:fix/99068-discord-chunk-reply-first

Conversation

@ZOOWH

@ZOOWH ZOOWH commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

With channels.discord.replyToMode: "first", chunked replies send the native Discord reply reference on ALL physical chunks — N notifications for one reply. The createReplyToFanout correctly gates at the inter-payload level, but the adapter wasn't passing replyToIdSource/replyToMode through.

Closes #99068

Root Cause / Fix

createReplyToFanout already handles single-use replies — it returns the reply id once and undefined for subsequent calls. But the Discord outbound adapter, sendText, and sendMedia didn't forward replyToIdSource and replyToMode from the delivery context through to sendMessageDiscord options.

Fix: pass replyToIdSource and replyToMode through the adapter → deliver options → send options chain. Also fix sendDiscordMedia trailing chunks to pass undefined instead of inheriting replyTo.

Evidence

Real Discord proof — chunks reply only on first message

# replyToMode: first, maxLinesPerMessage: 1 (5 chunks)
pnpm openclaw message send --channel discord \
  --target "channel:1522419241314156677" \
  --message "$(python3 -c "print('\n'.join(['chunk_' + str(i) for i in range(5)]))")" \
  --reply-to "1522427812919906404" --json

→ 5 platformMessageIds, first chunk has reply reference ✅
→ chunks 2-5 are plain messages (no reply) ✅

Screenshot: Discord client shows reply indicator only on "chunk_0".
image

Tests

pnpm test extensions/discord/
  44 tests passed

Change Type

  • Bug fix

Scope

  • Discord channel (send layer)

Security Impact

  • New permissions? No · Secrets changed? No · New network calls? No

@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord size: XS labels Jul 2, 2026
@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 3, 2026, 12:00 AM ET / 04:00 UTC.

Summary
The branch forwards Discord reply source/mode metadata into the Discord send layer and gates trailing native reply references for implicit single-use chunked text and media sends, with focused regression tests.

PR surface: Source +38, Tests +26. Total +64 across 5 files.

Reproducibility: yes. Current main is source-reproducible: sendDiscordText and sendDiscordMedia still pass the same replyTo into every internal chunk, and buildDiscordMessageRequest turns each present replyTo into message_reference; the linked issue also includes shipped-version logs.

Review metrics: 1 noteworthy metric.

  • Sibling Fix PRs: 3 open sibling PRs. Multiple open branches target the same Discord reply fanout issue, so maintainers need a canonical landing choice before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #99068
Summary: This PR is one of several candidate fixes for the canonical Discord replyToMode chunk fanout bug.

Members:

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

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

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

Rank-up moves:

  • [P2] Choose this PR or a sibling as the canonical fix for the linked Discord bug.
  • [P2] Let required checks pass or rerun green on the exact head before merge.

Risk before merge

  • [P1] Several open PRs target the same Discord reply fanout bug, so maintainers should choose one canonical branch before merging and then close or supersede the remaining candidates.
  • [P1] The patch intentionally changes native Discord reply-reference fanout for existing replyToMode users; explicit, all-mode, and source-aware implicit behavior should remain covered before merge.
  • [P1] The current head still has failing CI jobs. The inspected failures appear outside this Discord patch, but required checks still need a green rerun or maintainer handling before landing.

Maintainer options:

  1. Choose One Canonical Discord Fix (recommended)
    Pick this branch or one sibling as the single landing path for the linked Discord reply fanout bug, then close the remaining duplicate candidates after merge.
  2. Merge This Branch After Required Checks
    If maintainers choose this PR, wait for required checks to pass or be retried green on the exact head before merging.
  3. Pause This Branch If A Sibling Wins
    If a sibling PR becomes the maintainer-approved fix, keep this branch out of the merge queue and close it as superseded after that replacement lands.

Next step before merge

  • [P2] This open implementation PR has sufficient proof and no line-level findings, but it needs maintainer canonical-PR selection and green required checks rather than an automated repair.

Security
Cleared: The diff changes Discord send-layer option plumbing and tests only; it adds no dependency, workflow, secret, permission, or new network surface.

Review details

Best possible solution:

Land one canonical source-aware Discord-plugin fix for #99068 that gates only implicit single-use native reply references across internal chunks, then close the sibling candidate PRs after merge.

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

Yes. Current main is source-reproducible: sendDiscordText and sendDiscordMedia still pass the same replyTo into every internal chunk, and buildDiscordMessageRequest turns each present replyTo into message_reference; the linked issue also includes shipped-version logs.

Is this the best way to solve the issue?

Yes. The Discord send layer is the narrow maintainable boundary because shared outbound planning already consumes logical implicit replies, while the remaining fanout happens when Discord splits one send into physical chunks.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a normal-priority Discord delivery bug fix with limited channel-specific blast radius.
  • merge-risk: 🚨 compatibility: The PR changes native reply-reference behavior under an existing Discord replyToMode setting, so upgrade behavior for current users needs maintainer confidence.
  • merge-risk: 🚨 message-delivery: The diff changes when Discord message_reference is attached to physical message chunks, which can affect reply targeting and notifications.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The PR body includes after-fix real Discord CLI output and an inspected Discord client screenshot showing the chunked reply behavior, with private details redacted.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix real Discord CLI output and an inspected Discord client screenshot showing the chunked reply behavior, with private details redacted.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR body includes after-fix real Discord CLI output and an inspected Discord client screenshot showing the chunked reply behavior, with private details redacted.
Evidence reviewed

PR surface:

Source +38, Tests +26. Total +64 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 4 42 4 +38
Tests 1 29 3 +26
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 5 71 7 +64

What I checked:

  • Repository policy read: Root AGENTS.md and the scoped extensions guide were read fully; the plugin-boundary and whole-path PR review rules apply to this Discord plugin patch. (AGENTS.md:1, c37961fdab6e)
  • Current main reproduces the lower-level fanout: On current main, sendDiscordText closes over one replyTo value and passes it into every chunk request, so every physical chunk can become a native Discord reply. (extensions/discord/src/send.shared.ts:326, c37961fdab6e)
  • Current main media captions reuse the same replyTo: sendDiscordMedia sends trailing caption chunks through sendDiscordText while passing the same replyTo value, matching the issue's media follow-up root-cause path. (extensions/discord/src/send.shared.ts:423, c37961fdab6e)
  • Discord request builder contract: buildDiscordMessageRequest emits message_reference whenever replyTo is present, so chunk-level replyTo propagation is the native Discord reply/notification behavior. (extensions/discord/src/send.message-request.ts:90, c37961fdab6e)
  • Shared reply policy contract: createReplyToFanout treats implicit first/batched replies as single-use while preserving explicit replies, which is the invariant the Discord send-layer patch carries into internal chunks. (src/infra/outbound/reply-policy.ts:19, c37961fdab6e)
  • PR diff implements source-aware chunk gating: The PR adds replyToMode and replyToIdSource plumbing, then gates trailing sendDiscordText/sendDiscordMedia replyTo values with isSingleUseReplyToMode while preserving explicit and all-mode replies. (extensions/discord/src/send.shared.ts:331, 4ed58b70b3e5)

Likely related people:

  • steipete: Peter Steinberger authored the focused Discord replyToMode and explicit-reply behavior commits that define the invariant this PR preserves. (role: feature owner; confidence: high; commits: a32a3e23314d, 6b627d47070c, 9b7002ee5971; files: extensions/discord/src/monitor/reply-delivery.ts, extensions/discord/src/monitor/message-handler.batch-gate.ts, src/auto-reply/reply/reply-reference.ts)
  • scoootscooob: They moved the Discord channel implementation into extensions/discord, which is the owner boundary touched by this patch. (role: migration contributor; confidence: medium; commits: 5682ec37fada; files: extensions/discord/src/send.shared.ts, extensions/discord/src/send.outbound.ts, extensions/discord/src/outbound-adapter.ts)
  • vincentkoc: Recent git history around Discord replyTo chunk-mode tests and release stabilization includes Vincent Koc, making them a useful adjacent routing candidate for this behavior area. (role: recent adjacent contributor; confidence: medium; commits: e8786e0f0199, e085fa1a3ffd; files: extensions/discord/src, docs/channels/discord.md)
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. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jul 2, 2026
@ZOOWH

ZOOWH commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — wired suppressTrailingReply through outbound send context, media trailing respects mode, 5 test scenarios

@clawsweeper

clawsweeper Bot commented Jul 2, 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.

@ZOOWH

ZOOWH commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@ZOOWH

ZOOWH commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — derived from replyToMode (not helper option), all CI checks passed locally

@clawsweeper

clawsweeper Bot commented Jul 2, 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.

@clawsweeper clawsweeper Bot added status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 2, 2026
@ZOOWH

ZOOWH commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — real Discord proof attached: 5 chunks, only first carries reply reference

@clawsweeper

clawsweeper Bot commented Jul 3, 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.

@clawsweeper clawsweeper Bot added proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. labels Jul 3, 2026
@ZOOWH

ZOOWH commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — now consumes replyToMode in the chunking path via isSingleUseReplyToMode, preserves explicit/all, gates only implicit-first

@clawsweeper

clawsweeper Bot commented Jul 3, 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.

@ZOOWH

ZOOWH commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — squashed to single commit, send-layer gating + 4 regression tests + real Discord proof

@clawsweeper

clawsweeper Bot commented Jul 3, 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.

@clawsweeper clawsweeper Bot added status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 3, 2026
@ZOOWH
ZOOWH force-pushed the fix/99068-discord-chunk-reply-first branch from 73ca6ac to 14d1b71 Compare July 3, 2026 03:06
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. labels Jul 3, 2026
@ZOOWH
ZOOWH force-pushed the fix/99068-discord-chunk-reply-first branch 2 times, most recently from 86b3bf7 to cd0e536 Compare July 3, 2026 03:19
@ZOOWH

ZOOWH commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — source-aware gating (replyToIdSource + replyToMode), 4 regression tests, type constraints fixed

@clawsweeper

clawsweeper Bot commented Jul 3, 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.

@ZOOWH
ZOOWH force-pushed the fix/99068-discord-chunk-reply-first branch from cd0e536 to a882da2 Compare July 3, 2026 03:23
@ZOOWH

ZOOWH commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — rebased onto latest main, source-aware gating

@clawsweeper

clawsweeper Bot commented Jul 3, 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.

With replyToMode "first", only the first physical chunk should carry
the native Discord reply reference — the createReplyToFanout already
consumes the single-use id at the inter-payload level, but sendDiscordText
unconditionally passed replyTo to every chunk body.

Fix: consume replyToMode via isSingleUseReplyToMode in the direct
Discord send/chunking path so trailing chunks are plain messages for
implicit-first replies.  Preserve replyTo on all chunks for explicit
tags and replyToMode "all".  In sendDiscordMedia, gate trailing text
chunks on the same policy instead of always passing undefined.

Wire replyToIdSource and replyToMode from the outbound adapter through
outbound-payload to DiscordSendOpts so the send layer has the policy
metadata it needs.

Add four send-layer regression tests covering implicit-first text/media
gating plus explicit/all text/media preservation.
@ZOOWH
ZOOWH force-pushed the fix/99068-discord-chunk-reply-first branch from a882da2 to 4ed58b7 Compare July 3, 2026 03:27
@clawsweeper clawsweeper Bot removed proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 3, 2026
@ZOOWH

ZOOWH commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 3, 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.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. 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. labels Jul 3, 2026
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Superseded by #100784, landed as bfb89d3ea6531c27c6d91402c0a4a7a8462b7f3f.

The landed fix uses one source-aware { messageId, scope } model across text, media, components, voice, retries, fallbacks, and receipts. It passed focused Discord tests, extension type/lint gates, autoreview, and a live Discord API proof showing implicit-first references [true, false] versus reusable-all [true, true].

Thanks @ZOOWH for identifying and working on the reply fanout bug.

@steipete steipete closed this Jul 6, 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. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. 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.

[Bug]: Discord: replyToMode "first" — chunked reply sends the native reply reference on every physical chunk (reply-notification spam)

2 participants