Skip to content

fix(discord): limit implicit reply references to first chunk#99150

Closed
qingminglong wants to merge 10 commits into
openclaw:mainfrom
qingminglong:codex/issue-99068-discord-reply-chunks
Closed

fix(discord): limit implicit reply references to first chunk#99150
qingminglong wants to merge 10 commits into
openclaw:mainfrom
qingminglong:codex/issue-99068-discord-reply-chunks

Conversation

@qingminglong

@qingminglong qingminglong commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Closes #99068

What Problem This Solves

Fixes an issue where Discord users with replyToMode: "first" could receive one native reply notification per physical chunk when a single long agent reply was split into multiple Discord messages.

Why This Change Was Made

Discord now carries a source-aware first-chunk-only flag from outbound delivery into the shared send layer. The flag is only enabled for implicit single-use reply targets, so explicit [[reply_to_*]] replies and all-mode replies keep their existing reusable native reply behavior while the implicit turn reply no longer fans out across every physical text or media-caption chunk.

The payload send path now uses the same reply fanout contract for each physical Discord send, including audioAsVoice, native Discord payload media, component follow-up media, and classic component media downgrades. That closes the reviewer-noted gap where one captured implicit reply id could be reused across voice/text/media/component sends.

The related open PR #99083 is not a strong blocker for this replacement shape: it now has proof, but its mode-only suppression was previously flagged for collapsing explicit first-mode chunk replies. This PR uses the reply source to avoid that compatibility regression while keeping all-mode and explicit reply tags reusable.

User Impact

Long Discord replies in first/batched implicit reply modes now quote the source message only on the first physical Discord message, avoiding repeated reply notifications for one logical answer. Explicit reply tags and all-mode replies remain reusable across chunks and payload sends as before.

Evidence

  • Current merge-refresh head 9b39407fff2bdc464fd74c1a67e2f384a6e0f50a merges latest upstream/main into codex/issue-99068-discord-reply-chunks. The PR diff against current upstream/main remains Discord-only: 10 files, +173/-13.

  • Inspected exact failed QA Smoke CI run 28742547549, job 85227804770, artifact qa-smoke-profile-28742547549-1. The root failure was Control UI chat flow Playwright coverage in ui/src/e2e/chat-flow.e2e.test.ts, specifically scrolls a delayed pending send into view before the ACK resolves failing at ui/src/e2e/chat-flow.e2e.test.ts:981 with expected 0 to be greater than 200.

  • After the merge refresh, PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe" node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/e2e/chat-flow.e2e.test.ts --reporter=verbose passed on current head: 1 file, 20 tests.

  • After the merge refresh, node scripts/run-vitest.mjs extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.sends-basic-channel-messages.test.ts extensions/discord/src/send.components.test.ts --reporter=verbose passed on current head: 3 files, 102 tests.

  • git diff --check passed on current head.

  • Claim proved on PR code head 71ae6b987eb7b5124e8921f7789ff96b865986ba: a live Discord bot token sent real messages to a real Discord server/channel through sendMessageDiscord() -> sendDiscordText() -> createChannelMessage() using the PR code. The proof wrapper captured each real POST /channels/<channel>/messages request body at the Discord HTTP boundary while still sending the messages to Discord.

  • Merge-refresh head a9cabf230abeebff149847d2d2bc1026390f8a69 resolves the latest upstream main conflict. The resolution keeps upstream's per-send delivery progress reporting and this PR's source-aware implicit-reply first-chunk-only behavior; no Discord reply-fanout behavior was intentionally changed beyond the already-proved PR code.

  • Follow-up head 3f60b73dfca7e1428ec0338a332c3c5a4e63a55b resolves the July 4 ClawSweeper P2 by carrying replyToFirstChunkOnly into Discord component payload sends and through the classic component media downgrade into sendMessageDiscord(). The focused regression covers an implicit first-mode component media payload and the downgraded send options.

  • Live Discord proof command:

$env:DISCORD_BOT_TOKEN=[Environment]::GetEnvironmentVariable('DISCORD_BOT_TOKEN','User')
$env:OPENCLAW_DISCORD_GUILD_ID=[Environment]::GetEnvironmentVariable('OPENCLAW_DISCORD_GUILD_ID','User')
$env:OPENCLAW_DISCORD_CHANNEL_ID=[Environment]::GetEnvironmentVariable('OPENCLAW_DISCORD_CHANNEL_ID','User')
node --import tsx .tmp\discord-reply-chunk-proof.ts
  • Live Discord proof result, proof id pr-99150-1783017507939: seed message 1522310522282704988; implicit first-mode reply produced three physical Discord message POSTs with returned message ids 1522310535666864320, 1522310543938162912, 1522310550229614776 and request-body message_reference.message_id values [1522310522282704988, null, null].
  • Same live proof verified the control behavior for reusable native replies: all-mode produced three physical Discord message POSTs with returned message ids 1522310567916863508, 1522310574443073566, 1522310580915015712 and request-body message_reference.message_id values [1522310522282704988, 1522310522282704988, 1522310522282704988].
  • Claim proved by live Discord HTTP-boundary proof: Discord request bodies only include message_reference on the first physical chunk when the reply target is implicit single-use, while direct/default sends, explicit replies, and replyToMode: "all" keep their reusable native reply behavior.
  • Claim proved on PR code and rechecked after merge refresh: the audioAsVoice payload path no longer captures and reuses one implicit reply id across voice/text/media physical sends; the regression expects the voice send to keep the single implicit reply and later text/media sends to omit it.
  • Claim proved on PR code and rechecked after merge refresh: native Discord payload media sends propagate replyToFirstChunkOnly: true for implicit first-mode sends, so a direct payload send that internally chunks still suppresses trailing chunk native references.
  • Claim proved on current follow-up head: component payload media sends propagate replyToFirstChunkOnly: true for implicit first-mode sends, and classic component media downgrades forward that flag into sendMessageDiscord().
  • node scripts/run-vitest.mjs extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.sends-basic-channel-messages.test.ts src/infra/outbound/message-plan.test.ts --reporter=verbose passed on 71ae6b987eb7b5124e8921f7789ff96b865986ba: 3 files, 89 tests.
  • After the first merge refresh to e7bb7b7b7043a1fcfaefd331cd65525310ec18f4, focused Discord/outbound tests, oxfmt, git diff --check, plugin SDK boundary, and autoreview passed.
  • After the latest conflict refresh to a9cabf230abeebff149847d2d2bc1026390f8a69, node scripts/run-vitest.mjs extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.sends-basic-channel-messages.test.ts extensions/discord/src/send.components.test.ts --reporter=verbose passed: 3 files, 97 tests.
  • After follow-up head 3f60b73dfca7e1428ec0338a332c3c5a4e63a55b, node scripts/run-vitest.mjs extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.sends-basic-channel-messages.test.ts extensions/discord/src/send.components.test.ts --reporter=verbose passed: 3 files, 98 tests.
  • After follow-up head 3f60b73dfca7e1428ec0338a332c3c5a4e63a55b, pnpm exec oxfmt --check extensions/discord/src/outbound-payload.ts extensions/discord/src/send.components.ts extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.components.test.ts, git diff --check, and node scripts/check-extension-plugin-sdk-boundary.mjs --mode=src-outside-plugin-sdk passed.
  • After follow-up head 3f60b73dfca7e1428ec0338a332c3c5a4e63a55b, python .agents\skills\autoreview\scripts\autoreview --mode local exited 0 with no accepted/actionable findings.

Not tested / proof gaps:

  • I did not attach a Discord client screen recording of notification UI. The live proof instead verifies the exact Discord HTTP boundary that controls native reply fanout: implicit first-mode long replies sent only one message_reference, while all-mode sent one per physical chunk.
  • I did not rerun the live Discord HTTP-boundary proof on the merge-refresh or follow-up heads. A previous rerun attempt on this machine timed out connecting to discord.com:443 twice (UND_ERR_CONNECT_TIMEOUT), so the post-merge validation above rechecks the touched behavior locally.

@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord size: S 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 5, 2026, 10:22 AM ET / 14:22 UTC.

Summary
The PR threads source-aware first-chunk reply gating through Discord text, media, payload, component, and voice-adjacent send paths so implicit first/batched replies quote only the first physical chunk.

PR surface: Source +50, Tests +110. Total +160 across 10 files.

Reproducibility: yes. from source and reporter proof: current sendDiscordText and sendDiscordMedia pass the same replyTo into every chunk, matching the live issue logs for replyToMode "first".

Review metrics: 1 noteworthy metric.

  • Discord reply fanout option: 1 internal option added, 5 send surfaces threaded. The new flag changes native reply references across Discord text, media, payload, component, and voice-adjacent sends, which is the merge-sensitive behavior to review.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #99068
Summary: The canonical problem is the Discord replyToMode "first" chunked native-reply fanout bug; this PR is the strongest source-aware candidate fix, while several other open PRs target the same root cause.

Members:

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

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

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

Risk before merge

  • [P1] The PR intentionally changes which physical Discord sends include native message_reference, so the main merge risk is suppressing or duplicating Discord reply targeting if the source/mode gate is wrong.
  • [P1] Several open PRs target the same canonical bug, so maintainers should land one source-aware fix and close or supersede the alternatives rather than merging overlapping implementations.

Maintainer options:

  1. Land this source-aware fix (recommended)
    Accept the limited delivery risk after maintainer review because the patch narrows suppression to implicit single-use replies and has live HTTP-boundary proof plus focused regression coverage.
  2. Pause for a different canonical PR
    Pause or close this PR if maintainers choose another open implementation for the same Discord reply-fanout bug.

Next step before merge

  • [P2] No repair lane is needed; maintainer review should choose the canonical PR for the Discord reply-fanout bug and then handle the duplicate candidate PRs.

Maintainer decision needed

  • Question: Should this source-aware PR be the canonical fix for [Bug]: Discord: replyToMode "first" — chunked reply sends the native reply reference on every physical chunk (reply-notification spam) #99068 over the other open Discord reply-fanout PRs?
  • Rationale: Multiple active PRs patch the same message-delivery bug, and only a maintainer can choose the canonical implementation to land before closing the duplicates.
  • Likely owner: steipete — This owner candidate has the strongest history signal for the current Discord reply/send surface and related reply-mode commits.
  • Options:
    • Adopt this PR as canonical (recommended): Land this PR because it fixes implicit single-use chunk fanout while preserving explicit and all-mode reply behavior across text, media, payload, voice-adjacent, and component paths.
    • Choose another open PR: Pause this PR if maintainers prefer another duplicate fix and are ready to carry this PR's source-aware compatibility coverage into that path.
    • Hold for broader reply-policy work: Defer landing if maintainers want to redesign reply fanout across channel/plugin boundaries instead of fixing the Discord physical chunker now.

Security
Cleared: The diff only changes Discord TypeScript send plumbing and tests; it does not touch dependencies, workflows, secrets, permissions, lockfiles, or package metadata.

Review details

Best possible solution:

Land one source-aware Discord send-layer fix that preserves explicit and all-mode reply reuse, then close or supersede the duplicate candidate PRs for the same bug.

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

Yes from source and reporter proof: current sendDiscordText and sendDiscordMedia pass the same replyTo into every chunk, matching the live issue logs for replyToMode "first".

Is this the best way to solve the issue?

Yes; source-aware first-chunk gating at the Discord send layer is narrower than mode-only suppression because it preserves explicit reply tags and replyToMode "all".

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a normal-priority Discord channel delivery bug fix with limited blast radius and no data-loss, crash-loop, or security signal.
  • merge-risk: 🚨 message-delivery: The PR changes when Discord message_reference is sent, so an incorrect gate could suppress or duplicate native reply targeting.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes after-fix live Discord HTTP-boundary output showing implicit first-mode chunks send one message_reference, plus exact-head checks revalidating the touched behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix live Discord HTTP-boundary output showing implicit first-mode chunks send one message_reference, plus exact-head checks revalidating the touched behavior.
Evidence reviewed

PR surface:

Source +50, Tests +110. Total +160 across 10 files.

View PR surface stats
Area Files Added Removed Net
Source 7 56 6 +50
Tests 3 117 7 +110
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 10 173 13 +160

Acceptance criteria:

  • [P1] gh pr checks 99150.
  • [P1] Review PR body live Discord HTTP-boundary proof for implicit first-mode and all-mode controls.
  • [P1] Confirm duplicate PRs remain unmerged before landing one canonical fix.

What I checked:

  • Repository policy read: Root and scoped extension review policies were read fully and applied to the Discord plugin boundary and review-depth requirements. (AGENTS.md:1, 61ac4ec8a0db)
  • Current main repeats text chunk reply references: Current main builds every Discord text chunk request with the same closed-over replyTo value, so each physical text chunk can become a native reply. (extensions/discord/src/send.shared.ts:332, 61ac4ec8a0db)
  • Current main repeats media follow-up reply references: Current main sends the media caption with replyTo and then passes the same replyTo into follow-up text chunks for long captions. (extensions/discord/src/send.shared.ts:433, 61ac4ec8a0db)
  • Current payload path reused captured implicit replies: The current audioAsVoice payload path captures one resolved reply id and reuses it across voice, fallback text, visible text, and extra media sends. (extensions/discord/src/outbound-payload.ts:95, 61ac4ec8a0db)
  • Documented Discord reply contract: Discord docs define replyToMode first as attaching the implicit native reply reference to the first outbound Discord message of the turn, and all as attaching it to every outbound message. Public docs: docs/channels/discord.md. (docs/channels/discord.md:639, 61ac4ec8a0db)
  • Shared reply policy is source-aware: The shared reply fanout consumes implicit single-use reply ids once but keeps explicit replies reusable, matching the PR's preservation of explicit/all-mode behavior. (src/infra/outbound/reply-policy.ts:19, 61ac4ec8a0db)

Likely related people:

  • steipete: Current blame carries the Discord send chunk body through a recent broad commit, and earlier reply-mode commits add batched mode and explicit reply tag handling in the same behavior family. (role: recent area contributor; confidence: high; commits: 3f493aa6d015, 6b627d47070c, a32a3e23314d; files: extensions/discord/src/send.shared.ts, extensions/discord/src/outbound-send-context.ts, src/infra/outbound/reply-policy.ts)
  • vincentkoc: History for replyToMode shows adjacent Discord reply-mode test and stabilization work near this behavior family. (role: adjacent test contributor; confidence: medium; commits: e8786e0f0199, 9e641af011a8, e085fa1a3ffd; files: extensions/discord/src/outbound-adapter.test.ts, extensions/discord/src/send.sends-basic-channel-messages.test.ts)
  • scoootscooob: History shows the Discord channel implementation was moved into extensions under this contributor, which is relevant boundary context for the current plugin-owned send path. (role: migration contributor; confidence: low; commits: 5682ec37fada; files: extensions/discord/src/send.shared.ts, extensions/discord/src/outbound-adapter.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.
Review history (8 earlier review cycles)
  • reviewed 2026-07-03T07:25:14.678Z sha e7bb7b7 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-04T15:31:47.507Z sha a9cabf2 :: needs changes before merge. :: [P2] Thread the flag through component media downgrades
  • reviewed 2026-07-04T15:38:43.191Z sha a9cabf2 :: needs changes before merge. :: [P2] Thread the flag through component media downgrades
  • reviewed 2026-07-04T17:24:55.368Z sha 3f60b73 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-04T17:33:38.506Z sha 3f60b73 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T12:03:03.025Z sha 6402a9b :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T13:47:30.424Z sha 4f3c90d :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T14:12:04.529Z sha 9b39407 :: needs maintainer review before merge. :: none

@harjothkhara

Copy link
Copy Markdown
Contributor

I think this is the stronger lane than #99083 because it keeps the Discord sender opt-in/source-aware, so explicit reply tags and all-mode behavior do not get collapsed by a mode-only sender check. One gap I would still close before landing: the audioAsVoice payload path still captures one implicit reply id and reuses it for multiple physical Discord sends.

extensions/discord/src/outbound-payload.ts:86-114 does const voiceReplyTo = sendContext.resolveReplyTo() once, then passes that same value to sendVoice, the optional text send, and every additional media send. But createDiscordPayloadSendContext wires resolveReplyTo to createReplyToFanout (extensions/discord/src/outbound-send-context.ts:77-81), whose contract consumes implicit single-use first/batched ids once unless the source is explicit (src/infra/outbound/reply-policy.ts:19-38). Reusing the captured value bypasses that policy, so an implicit replyToMode: "first"/"batched" audio-as-voice payload with text or multiple media URLs can still emit multiple native reply references. That is the same notification-spam class as #99068; the Discord docs promise first attaches the implicit native reply to the first outbound Discord message only (docs/channels/discord.md:659-661).

I would either thread the same source-aware first-chunk/first-physical-send signal through this branch, or stop reusing voiceReplyTo after the first physical send for implicit single-use modes while preserving explicit [[reply_to_*]] behavior. A focused regression in extensions/discord/src/outbound-adapter.test.ts for audioAsVoice + replyToIdSource: "implicit" + replyToMode: "first"/"batched" would cover the remaining gap.

@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. labels Jul 2, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

Updated the PR body for current head 71ae6b987eb7b5124e8921f7789ff96b865986ba with the new focused evidence and the remaining live-proof gap.

This head also fixes the reviewer-noted audioAsVoice gap: implicit first/batched reply targets now flow through the same per-physical-send fanout instead of reusing one captured reply id across voice/text/media sends.

@openclaw-mantis visual task: verify Discord replyToMode first quotes only the first long-reply chunk while explicit first/all chunk replies keep expected native references.

@clawsweeper re-review

@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 P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jul 2, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. 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. and removed 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. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 2, 2026
…e-main

# Conflicts:
#	extensions/discord/src/outbound-send-context.ts
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 3, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

Merge-refresh pushed to clear the DIRTY state on current main.

Updated the PR body for head e7bb7b7b7043a1fcfaefd331cd65525310ec18f4 with the merge-resolution note and fresh post-merge validation:

  • node scripts/run-vitest.mjs extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.sends-basic-channel-messages.test.ts --reporter=verbose passed: 2 files, 85 tests.
  • node scripts/run-vitest.mjs src/infra/outbound/message-plan.test.ts --reporter=verbose passed: 1 file, 4 tests.
  • pnpm exec oxfmt --check extensions/discord/src/outbound-payload.ts extensions/discord/src/outbound-send-context.ts extensions/discord/src/outbound-adapter.ts extensions/discord/src/outbound-adapter.test.ts passed.
  • git diff --check HEAD~1..HEAD passed.
  • node scripts/check-extension-plugin-sdk-boundary.mjs --mode=src-outside-plugin-sdk passed.

The merge conflict was only in extensions/discord/src/outbound-send-context.ts; the resolution keeps upstream createLazyRuntimeModule and this PR's source-aware isSingleUseReplyToMode gate. The existing live Discord HTTP-boundary proof remains in the PR body, and GitHub's Real behavior proof check is green on this new head.

@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 the rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. label Jul 3, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

Have a nice day, please help merge!

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 4, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Follow-up pushed at 3f60b73dfca7e1428ec0338a332c3c5a4e63a55b for the July 4 P2. The component payload path now carries replyToFirstChunkOnly into sendDiscordComponentMessageLazy, and the classic component media downgrade forwards it into sendMessageDiscord().

Current-head proof added to the PR body:

  • node scripts/run-vitest.mjs extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.sends-basic-channel-messages.test.ts extensions/discord/src/send.components.test.ts --reporter=verbose passed: 3 files, 98 tests.
  • pnpm exec oxfmt --check extensions/discord/src/outbound-payload.ts extensions/discord/src/send.components.ts extensions/discord/src/outbound-adapter.test.ts extensions/discord/src/send.components.test.ts passed.
  • git diff --check passed.
  • node scripts/check-extension-plugin-sdk-boundary.mjs --mode=src-outside-plugin-sdk passed.
  • python .agents\skills\autoreview\scripts\autoreview --mode local exited 0 with no accepted/actionable findings.

@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 4, 2026
@clawsweeper

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

@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 4, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

PR body formatting is repaired on the same head 3f60b73dfca7e1428ec0338a332c3c5a4e63a55b. The previous body edit accidentally collapsed Markdown line breaks, which made the Real behavior proof context gate miss the authored What Problem This Solves and Evidence headings.

I rewrote the full PR body as multiline Markdown and checked it locally with the same policy helper: evaluatePullRequestContext(...) now returns passed with External PR includes problem context and evidence. The code/proof state is unchanged from the prior comment.

@clawsweeper clawsweeper Bot added 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. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 4, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

@clawsweeper automerge

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jul 5, 2026
@steipete steipete self-assigned this Jul 6, 2026
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Superseded by #100784, landed as bfb89d3ea6531c27c6d91402c0a4a7a8462b7f3f.

The landed fix retains this PR's core first-chunk correction and contributor credit, then broadens it into 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 @qingminglong for the original fix and regression coverage.

@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: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor 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)

3 participants