openclaw - ✅(Solved) Fix [Bug]: Slack subagent completion still leaks a top-level channel post after 2026.4.10 thread-routing fixes [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#65043Fetched 2026-04-12 13:25:51
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Author
Timeline (top)
commented ×1cross-referenced ×1labeled ×1

Slack subagent completions spawned from a thread still produce a duplicate top-level channel post on 2026.4.10, even though thread routing appears to be preserved correctly in the main delivery path.

This looks related to #64454, but not identical.

  • #64454 described the older 2026.4.9 behavior where the completion simply landed in the main channel instead of the thread.
  • On 2026.4.10, I now get:
    • one correct in-thread message
    • one leaked top-level channel message

So this appears to be a remaining edge/regression after the 2026.4.10 fixes.

Root Cause

Slack subagent completions spawned from a thread still produce a duplicate top-level channel post on 2026.4.10, even though thread routing appears to be preserved correctly in the main delivery path.

This looks related to #64454, but not identical.

  • #64454 described the older 2026.4.9 behavior where the completion simply landed in the main channel instead of the thread.
  • On 2026.4.10, I now get:
    • one correct in-thread message
    • one leaked top-level channel message

So this appears to be a remaining edge/regression after the 2026.4.10 fixes.

Fix Action

Fix / Workaround

  1. NO_REPLY is not fully suppressing announce-sourced deliver: true reply dispatch
  2. announce-sourced runs are emitting both:
    • the intended threaded delivery
    • a second unthreaded final/user delivery

Likely relevant files

  • src/agents/subagent-announce.ts
  • src/agents/subagent-announce-delivery.ts
  • src/gateway/server-methods/agent.ts
  • src/auto-reply/reply/dispatch-from-config.ts
  • src/auto-reply/reply/reply-delivery.ts
  • src/infra/outbound/deliver.ts
  • extensions/slack/src/outbound-adapter.ts
  • extensions/slack/src/send.ts

PR fix notes

PR #65077: fix(slack): keep reply suppression thread-aware

Description (problem / solution / changelog)

Summary

  • Problem: generic message tool send tracking dropped threadId, so same-channel Slack sends were treated as the same destination even when they targeted a different thread.
  • Why it matters: a threaded send could suppress the final reply for another Slack thread in the same channel, which is the wrong conversation boundary.
  • What changed: preserved threadId for generic message sends and threaded the originating thread id through both direct and followup reply-suppression checks, with focused Slack regression coverage.
  • What did NOT change (scope boundary): this does not alter Slack outbound transport, thread routing, or the existing NO_REPLY completion-delivery behavior.

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

  • Closes #
  • Related #65043
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: generic message-tool send extraction recorded provider/account/target but dropped threadId, and the fallback suppression path treated same-channel Slack sends as matching even when the originating turn was in a different thread.
  • Missing detection / guardrail: we had same-target suppression coverage, but no Slack regression test proving that same channel plus different thread must not suppress.
  • Contributing context (if known): the current #65043 duplicate top-level-post path appears fixed on main; this patch hardens the remaining thread-aware suppression edge in the same Slack/threaded completion family.

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/agents/pi-embedded-subscribe.tools.extract.test.ts, src/auto-reply/reply/reply-payloads.test.ts, src/auto-reply/reply/followup-delivery.test.ts, src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts
  • Scenario the test should lock in: Slack same-channel sends only suppress replies when the thread id also matches; different-thread sends in the same channel must still allow the final reply.
  • Why this is the smallest reliable guardrail: the bug spans tool-send extraction plus both direct and queued reply-suppression paths, so these focused tests lock each seam without needing live Slack coverage.
  • Existing test that already covers this (if any): existing same-target suppression tests covered provider/account matching but not Slack thread mismatches.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Slack threaded agent runs no longer let a message tool send in one thread suppress the final reply for a different thread in the same channel.

Diagram (if applicable)

Before:
[Slack thread A send via message tool] -> [same channel match] -> [thread B final reply suppressed]

After:
[Slack thread A send via message tool] -> [channel match + thread mismatch] -> [thread B final reply still delivered]

Security Impact (required)

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

Repro + Verification

Environment

  • OS: macOS 15 / Darwin 25.3.0
  • Runtime/container: local repo checkout
  • Model/provider: N/A
  • Integration/channel (if any): Slack reply suppression path
  • Relevant config (redacted): Slack channel replies with threaded agent runs / message tool sends

Steps

  1. Start in one Slack thread and trigger a message tool send to that thread.
  2. Continue a separate Slack thread in the same channel.
  3. Let the runner evaluate same-target suppression for the second thread.

Expected

  • Only the matching Slack thread can suppress its own reply.
  • A different Slack thread in the same channel still receives its final reply.

Actual

  • Before this fix, same-channel comparison could suppress across Slack threads because the generic message tool send metadata did not keep threadId.

Evidence

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

Human Verification (required)

  • Verified scenarios: preserved threadId from generic message sends; Slack same-channel same-thread suppression; Slack same-channel different-thread non-suppression; direct runner suppression path; followup suppression path.
  • Edge cases checked: account mismatch still does not suppress; non-threaded same-target suppression remains unchanged; NO_REPLY completion suppression remains covered separately.
  • What you did not verify: live Slack manual repro.

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)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: provider-specific suppression rules could diverge from the generic fallback for threaded channels.
    • Mitigation: the change only tightens the non-plugin fallback path and adds focused Slack regression coverage around both direct and queued delivery.

Additional Notes

  • AI-assisted: yes.
  • Testing depth: focused regression tests passed, pnpm build passed, pnpm format passed.
  • Broader repo gates: pnpm check and the support-boundary lane inside OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test still hit an unrelated existing Codex TypeScript failure in extensions/codex/src/app-server/dynamic-tools.ts (prepareArguments missing on AnyAgentTool).

Made with Cursor

Changed files

  • CHANGELOG.md (modified, +2/-0)
  • src/agents/pi-embedded-subscribe.tools.extract.test.ts (modified, +25/-0)
  • src/agents/pi-embedded-subscribe.tools.ts (modified, +8/-1)
  • src/auto-reply/reply/agent-runner-payloads.ts (modified, +2/-0)
  • src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts (modified, +39/-1)
  • src/auto-reply/reply/agent-runner.ts (modified, +1/-0)
  • src/auto-reply/reply/followup-delivery.test.ts (modified, +53/-0)
  • src/auto-reply/reply/followup-delivery.ts (modified, +2/-0)
  • src/auto-reply/reply/followup-runner.ts (modified, +1/-0)
  • src/auto-reply/reply/reply-payloads-dedupe.ts (modified, +26/-2)
  • src/auto-reply/reply/reply-payloads.test.ts (modified, +75/-0)

PR #65577: Outbound: centralize payload normalization semantics

Description (problem / solution / changelog)

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: outbound payload normalization semantics were duplicated across delivery prep, JSON output shaping, and mirror summary paths.
  • Why it matters: duplicated keep/drop and text/media parsing behavior increases recurring drift risk (for silent tokens, MEDIA: directives, reasoning suppression, and metadata preservation), which can cause regressions across channels and call sites.
  • What changed: introduced a single channel-agnostic OutboundPayloadPlan authority in src/infra/outbound/payloads.ts and migrated delivery/mirror/JSON call sites to consume plan projections.
  • What did NOT change (scope boundary): explicit-send literal behavior, queue raw persistence, adapter/channel transforms, heartbeat/cron semantics, and delivery transport ordering/side effects.

Background / Issue Signal

  • Recent issue signal for this hotspot included #65002, #64665, #64976, #65043, #64971, #65315, and #65375 (recurring outbound text/media/silent/routing drift across entry paths).
  • Hotspot evidence: src/infra/outbound/deliver.ts remains a large seam (~804 LOC), historically combining normalization concerns with transport concerns.
  • Cross-cutting semantics have broad fan-out (resolveSendableOutboundReplyParts appears in ~30 non-test usages, isSilentReplyText in ~21, and stripHeartbeatToken in ~11), which is where drift pressure comes from.
  • This PR centralizes outbound normalization at the outbound seam while intentionally keeping heartbeat/cron-specific semantics separate, consistent with the approved constraints.

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

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: outbound normalization logic evolved in multiple places with overlapping responsibilities, so behavior updates could land in one path but not others.
  • Missing detection / guardrail: insufficient cross-path characterization tests tying delivery, mirror, and JSON behavior to the same expected semantics.
  • Contributing context (if known): multiple independent call sites (deliver.ts, send.ts, message.ts, agents/command/delivery.ts) each needed normalization outputs.

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/infra/outbound/payloads.test.ts
    • src/infra/outbound/deliver.test.ts
    • src/infra/outbound/message.test.ts
  • Scenario the test should lock in:
    • silent token exact/mixed/JSON keep/drop semantics
    • media + silent combinations
    • directive parsing (MEDIA:, reply tags, audioAsVoice)
    • reasoning suppression
    • channelData-only handling
    • mirror parity and queue raw persistence
  • Why this is the smallest reliable guardrail: these tests assert behavior at the normalization boundary and at transport/mirror seams where drift previously occurred.
  • Existing test that already covers this (if any): existing outbound delivery/message/send tests were extended and retained.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

None intended. This is behavior-preserving centralization with stronger regression coverage.

Diagram (if applicable)

Before:
caller -> local normalization variant A/B/C -> delivery/json/mirror

After:
caller -> OutboundPayloadPlan (single normalization authority) -> projection (delivery/json/mirror)

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: Node 24 + pnpm workspace
  • Model/provider: N/A
  • Integration/channel (if any): outbound channel adapters (test doubles)
  • Relevant config (redacted): test configs only

Steps

  1. Run outbound normalization and seam tests.
  2. Verify queue write-ahead behavior remains raw.
  3. Verify mirror matrix behavior for MEDIA: and silent token variants.

Expected

  • Delivery/JSON/mirror semantics stay stable while normalization authority is centralized.

Actual

  • Matches expected in targeted suites.

Evidence

Attach at least one:

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

Key verification commands run:

  • pnpm test src/infra/outbound/payloads.test.ts src/infra/outbound/deliver.test.ts src/infra/outbound/message.test.ts src/gateway/server-methods/send.test.ts src/agents/command/delivery.test.ts
  • pnpm test src/infra/outbound/deliver.test.ts src/infra/outbound/message.test.ts

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • plan projections preserve existing normalization behavior for delivery/json/mirror
    • queue persistence stays raw at enqueue boundary
    • sendMessage mirror outcomes for directive/silent-token matrix remain as expected
    • delivery transport flow still runs queue/hook/adapter paths in existing order
  • Edge cases checked:
    • exact + JSON silent tokens
    • silent token with media
    • MEDIA: directive extraction
    • channelData-only payload behavior
  • What you did not verify:
    • full monorepo pnpm test green state (repository currently has unrelated failing shards outside this change surface)

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.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

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: subtle normalization drift in less-common payload combinations.
    • Mitigation: added characterization + matrix tests with fixed expected outputs at queue and mirror seams.
  • Risk: future expansion could accidentally move channel-specific logic into plan.
    • Mitigation: explicit plan boundary comments and keeping adapter transforms in deliver.ts.

Changed files

  • src/agents/command/delivery.ts (modified, +6/-4)
  • src/gateway/server-methods/send.ts (modified, +10/-13)
  • src/infra/outbound/deliver.test.ts (modified, +30/-0)
  • src/infra/outbound/deliver.ts (modified, +16/-24)
  • src/infra/outbound/message.test.ts (modified, +103/-0)
  • src/infra/outbound/message.ts (modified, +10/-10)
  • src/infra/outbound/payloads.test.ts (modified, +238/-0)
  • src/infra/outbound/payloads.ts (modified, +132/-54)
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

Bug type

Behavior bug (incorrect output/state without crash)

Summary

Slack subagent completions spawned from a thread still produce a duplicate top-level channel post on 2026.4.10, even though thread routing appears to be preserved correctly in the main delivery path.

This looks related to #64454, but not identical.

  • #64454 described the older 2026.4.9 behavior where the completion simply landed in the main channel instead of the thread.
  • On 2026.4.10, I now get:
    • one correct in-thread message
    • one leaked top-level channel message

So this appears to be a remaining edge/regression after the 2026.4.10 fixes.

OpenClaw version

2026.4.10 (44e5b62)

Operating system

Linux 6.17.0-1010-aws (x64)

Channel / provider

Slack Socket Mode

Model

Router: anthropic/claude-opus-4-6
Subagents: anthropic/claude-sonnet-4-6

Model choice does not appear relevant. This looks like delivery/routing logic.

Relevant config

md json5 { channels: { slack: { replyToMode: "all", replyToModeByChatType: { channel: "all", group: "all" }, streaming: { mode: "partial", nativeTransport: true }, groupPolicy: "allowlist", channels: { "C0AJFUC6Z0R": { enabled: true } }, }, }, subagents: { maxConcurrent: 8, model: "anthropic/claude-sonnet-4-6", }, } I also tested with: json5 streaming: { mode: "off", nativeTransport: false } and the leak still occurs, so this is not a streaming-only issue.

Steps to reproduce

  1. Reply inside an existing Slack thread mentioning the bot
  2. From that thread session, spawn a subagent via sessions_spawn
  3. When the completion arrives, the responding agent uses the message tool with the correct threadId to post the result in-thread, then returns exact NO_REPLY
  4. Wait for completion delivery

Expected behavior

Exactly one message in the originating Slack thread.

Actual behavior

Two messages are produced:

  • one correct in-thread message
  • one leaked top-level channel message with no thread_ts

Example leaked message:

json { "ts": "1775936636.290499", "text": "✅ Round 3 complete..." }

Evidence

Session store has the correct lastThreadId for both the thread session and the subagent session:

  • thread session: 1775934442.601329
  • subagent session: 1775934442.601329

What I verified

I traced thread propagation and it appears correct through the primary path:

  1. spawn captures requesterOrigin.threadId
  2. announce registry merges lastThreadId
  3. announce delivery passes threadId into resolveExternalBestEffortDeliveryTarget()
  4. agent handler receives explicit request.threadId
  5. delivery plan resolves threadId
  6. Slack reply transport maps thread id to replyToId
  7. outbound delivery passes replyToId
  8. Slack sender supports thread_ts on chat.postMessage

Despite that, Slack still receives a top-level post without thread_ts.

Confirmed non-causes

  • not reply_broadcast
  • not Slack streaming
  • not replyToMode
  • not session-store thread state
    • not a missing lastThreadId

Suspected cause

This now looks less like pure thread-id loss and more like a duplicate delivery path:

  • one payload is correctly threaded
  • another payload is emitted separately and sent without replyToId/threadId

Two likely areas:

  1. NO_REPLY is not fully suppressing announce-sourced deliver: true reply dispatch
  2. announce-sourced runs are emitting both:
    • the intended threaded delivery
    • a second unthreaded final/user delivery

Likely relevant files

  • src/agents/subagent-announce.ts
  • src/agents/subagent-announce-delivery.ts
  • src/gateway/server-methods/agent.ts
  • src/auto-reply/reply/dispatch-from-config.ts
  • src/auto-reply/reply/reply-delivery.ts
  • src/infra/outbound/deliver.ts
  • extensions/slack/src/outbound-adapter.ts
  • extensions/slack/src/send.ts

Related issue

  • #64454 This feels like the same bug family, but not the same exact failure mode:
  • #64454: completion routed only to main channel
  • this issue: threaded delivery now works, but an extra top-level post still leaks
<img width="2517" height="707" alt="Image" src="https://github.com/user-attachments/assets/90d7206d-e169-459b-9ccd-4ed0b9b7d023" />

Steps to reproduce

  1. In 2026.4.10 start a Slack thread.
  2. Ask the thread to start a sub-agent.
  3. The sub-agent announces back to thread and main.

Expected behavior

    1. In 2026.4.10 start a Slack thread.
  1. Ask the thread to start a sub-agent.
  2. The sub-agent announces only back to thread and not main.

Actual behavior

  1. In 2026.4.10 start a Slack thread.
  2. Ask the thread to start a sub-agent.
  3. The sub-agent announces back to thread and main.

OpenClaw version

OpenClaw 2026.4.10 (44e5b62)

Operating system

Linux 6.17.0-1010-aws (x64)

Install method

npm global

Model

Opus 4.6 / Sonnet 4/6

Provider / routing chain

anthropic

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

Its not breaking, but it create a lot of channel noise and is very annoying.

Additional information

No response

extent analysis

TL;DR

The most likely fix for the duplicate top-level channel post issue is to investigate and modify the NO_REPLY handling in the subagent-announce.ts and subagent-announce-delivery.ts files to prevent duplicate delivery paths.

Guidance

  1. Review NO_REPLY handling: Investigate how NO_REPLY is handled in the subagent-announce.ts and subagent-announce-delivery.ts files to ensure it fully suppresses announce-sourced deliver: true reply dispatch.
  2. Check for duplicate delivery paths: Examine the code to identify if announce-sourced runs are emitting both intended threaded delivery and a second unthreaded final/user delivery, and modify the code to prevent this.
  3. Verify thread ID propagation: Double-check that the thread ID is correctly propagated through the primary path, from requesterOrigin.threadId to replyToId, to ensure that the issue is not caused by a thread ID loss.
  4. Test with modified replyToMode: Try modifying the replyToMode configuration to see if it affects the behavior, although it has been confirmed as not the cause, it might be worth re-testing in conjunction with other changes.

Example

No code example is provided as the issue requires a deeper investigation of the codebase and modification of specific files.

Notes

The issue seems to be related to a duplicate delivery path, and modifying the NO_REPLY handling and checking for duplicate delivery paths should help resolve the issue. However, without access to the codebase, it's difficult to provide a more specific solution.

Recommendation

Apply a workaround by modifying the NO_REPLY handling and checking for duplicate delivery paths, as the root cause of the issue seems to be related to the announce-sourced delivery logic.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

    1. In 2026.4.10 start a Slack thread.
  1. Ask the thread to start a sub-agent.
  2. The sub-agent announces only back to thread and not main.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: Slack subagent completion still leaks a top-level channel post after 2026.4.10 thread-routing fixes [2 pull requests, 1 comments, 2 participants]