openclaw - ✅(Solved) Fix silent-reply detection gated behind isGroupChat — direct chats can't honor silentReplyPolicy="allow" [3 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#74409Fetched 2026-04-30 06:24:14
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
2
Timeline (top)
cross-referenced ×3closed ×1commented ×1

In the bundled get-reply-eY9NJdyX.js (source src/auto-reply/.../get-reply.ts), allowEmptyAssistantReplyAsSilent is gated behind isGroupChat &&, which makes it impossible to honor silentReplyPolicy: "allow" on direct (DM) chats. Empty/silent-token replies on direct chats always trigger the empty-response retry → fallback chain, even when configured to be allowed.

Error Message

Send a heartbeat-style ping to a direct DM where the model would correctly respond with NO_REPLY. Observed: harness logs empty response detected → retrying with visible-answer continuation → empty response retries exhausted → incomplete turn detected stopReason=stop payloads=0 → surfacing error to user. Expected: silent absorption, since policy is "allow".

Root Cause

May share root cause / overlap with:

  • #66459 — Telegram transcript contains final reply but no outbound send occurs for [thinking,text] turn
  • #72541 — Gateway completeness check false-negative on pure-relay agents
  • #73020 — Empty-response fallback leaks provider validation errors to users (closed; possibly regressed)
  • #74021 — Support reasoning-field outputs and visible final-answer handling

Fix Action

Fix / Workaround

(Confirmed locally — patched the bundled JS, silentReply policy is now respected on direct chats.)

PR fix notes

PR #74420: fix(auto-reply): honour silentReplyPolicy=allow on direct/DM chats (#74409)

Description (problem / solution / changelog)

Root cause

allowEmptyAssistantReplyAsSilent in get-reply-run.ts was gated behind isGroupChat &&, so resolveGroupSilentReplyBehavior was never called for direct/DM conversations. The function itself already returns silentReplyPolicy === "allow" regardless of conversation type — the outer isGroupChat && was the sole blocker.

Result: direct-chat heartbeats with silentReply.direct: "allow" still hit the empty-response retry → fallback cascade and surfaced as user-facing errors, even though policy explicitly permitted silent absorption.

Fix

Remove the isGroupChat && short-circuit. resolveGroupSilentReplyBehavior now runs for all conversation types and its return value drives allowEmptyAssistantReplyAsSilent unconditionally.

Tests

  • Renamed existing "does not propagate empty-assistant silence for direct runs" test to clarify it tests the default (no policy configured, silentReplyPolicy resolves to "disallow") — still passes, false unchanged.
  • Added new test: "propagates empty-assistant silence for direct runs when silentReplyPolicy is allow (#74409)" — verifies allowEmptyAssistantReplyAsSilent: true when agents.defaults.silentReply.direct: "allow".

41/41 tests pass. Lint clean.

Fixes #74409.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/auto-reply/reply/get-reply-run.media-only.test.ts (modified, +35/-1)
  • src/auto-reply/reply/get-reply-run.ts (modified, +6/-8)

PR #74446: fix(auto-reply): honor direct silent reply allow policy

Description (problem / solution / changelog)

Summary

  • Problem: direct chats configured with silentReply.direct: "allow" still could not propagate allowEmptyAssistantReplyAsSilent because the flag was short-circuited behind isGroupChat.
  • Why it matters: clean empty or reasoning-only direct-chat turns could enter empty-response retry/fallback even though the resolved direct policy explicitly allowed silence.
  • What changed: remove the group-only short-circuit, add direct-allow regression coverage, and clarify the docs/changelog around the default-vs-opt-in behavior.
  • What did NOT change (scope boundary): direct chats still disallow/rewrite silence by default; no config keys, schemas, APIs, runner parameters, or outbound normalization behavior changed.

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 #74409
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: runPreparedReply computed allowEmptyAssistantReplyAsSilent as isGroupChat && resolveGroupSilentReplyBehavior(...).allowEmptyAssistantReplyAsSilent, so direct/dm prompt contexts always passed false even when the resolved silent reply policy was allow.
  • Missing detection / guardrail: the existing direct-run test only covered the default disallow behavior, not the explicit direct allow configuration.
  • Contributing context (if known): issue #74409 reported the bundled runtime symptom and pointed at the source gate.

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/get-reply-run.media-only.test.ts
  • Scenario the test should lock in: a direct Slack-style prompt with agents.defaults.silentReply.direct = "allow" propagates allowEmptyAssistantReplyAsSilent: true to the followup runner, while the default direct case stays false.
  • Why this is the smallest reliable guardrail: it verifies the caller-side policy propagation where the bug lived without requiring live channel/provider traffic.
  • Existing test that already covers this (if any): src/agents/pi-embedded-runner/run.incomplete-turn.test.ts already covers the runner behavior once the flag is set.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Direct chats still disallow silence by default. If an operator explicitly configures silentReply.direct: "allow", clean empty or reasoning-only assistant turns can now be treated as intentional silent replies instead of triggering empty-response fallback.

Diagram (if applicable)

Before:
direct allow policy -> isGroupChat false -> runner flag false -> empty-response fallback

After:
direct allow policy -> resolved policy allow -> runner flag true -> silent NO_REPLY absorption

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 local checkout
  • Runtime/container: repo pnpm scripts, Node 22-compatible project runtime
  • Model/provider: N/A for local unit coverage
  • Integration/channel (if any): Slack-style direct-chat fixture
  • Relevant config (redacted): agents.defaults.silentReply.direct = "allow"

Steps

  1. Configure a direct-chat session with silentReply.direct: "allow".
  2. Run a direct prompt where the assistant produces a clean empty or reasoning-only silent turn.
  3. Observe whether allowEmptyAssistantReplyAsSilent reaches the embedded runner.

Expected

  • Explicit direct allow propagates allowEmptyAssistantReplyAsSilent: true.
  • Default direct behavior remains false.

Actual

  • Before this change, direct chats always propagated false because of the isGroupChat guard.
  • After this change, explicit direct allow propagates true while the default direct case remains false.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)
  • Added regression test plus passing local validation commands below

Human Verification (required)

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

  • Verified scenarios: default direct stays false; explicit direct allow becomes true; existing group allow remains covered.
  • Edge cases checked: direct default/disallow behavior, runner-side empty/reasoning-only handling, outbound payload normalization coverage.
  • What you did not verify: live Slack/Telegram provider traffic; Testbox/Blacksmith was not available in this environment, so pnpm check:changed was run locally.

Commands run:

  • pnpm test src/auto-reply/reply/get-reply-run.media-only.test.ts src/auto-reply/reply/groups.test.ts src/agents/pi-embedded-runner/run.incomplete-turn.test.ts src/infra/outbound/payloads.test.ts
  • pnpm exec oxfmt --check --threads=1 src/auto-reply/reply/get-reply-run.ts src/auto-reply/reply/get-reply-run.media-only.test.ts docs/concepts/messages.md docs/channels/groups.md CHANGELOG.md
  • git diff --check
  • pnpm check:changed
  • codex review --base origin/main
  • /Users/vyctor/.codex/skills/oss-pr-contributions/scripts/public_pr_preflight.sh /Users/vyctor/Documents/Codex/2026-04-29/https-github-com-openclaw-openclaw-issues/openclaw

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.

No bot review conversations exist on this PR yet.

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: direct chats could become silently quiet in more cases than before.
    • Mitigation: the change is gated by the existing explicit silentReply.direct: "allow" policy; default direct behavior remains disallow/rewrite.

AI-assisted: yes. I reviewed the diff, ran the validation above, and understand the code path this changes.

Changed files


PR #74555: fix(auto-reply): honor direct silent empty replies

Description (problem / solution / changelog)

Summary

  • Problem: silentReply.direct: "allow" told direct-chat prompts to use NO_REPLY, but empty or reasoning-only direct turns were still treated as failed empty responses.
  • Why it matters: operators who explicitly opt direct chats into silence should get the same intentional-silence handling as NO_REPLY, without changing conservative direct-chat defaults.
  • What changed: direct/dm runs now pass allowEmptyAssistantReplyAsSilent only when the resolved prompt-policy context is direct and silentReply.direct resolves to allow.
  • What did NOT change (scope boundary): group/channel behavior, default direct-chat behavior, silent rewrite behavior, and cross-session native command targeting remain conservative.

AI-assisted: yes. I understand what the code changes do.

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 #74409
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: the prompt path resolved direct silent-reply policy, but allowEmptyAssistantReplyAsSilent was gated behind isGroupChat only.
  • Missing detection / guardrail: coverage checked group empty/reasoning-only silence and direct prompt text, but not direct runner propagation for explicit silentReply.direct: "allow".
  • Contributing context (if known): direct chats intentionally default to disallow, so the fix must only apply to explicit direct policy context.

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/get-reply-run.media-only.test.ts
  • Scenario the test should lock in: direct and dm runs propagate empty-assistant silence only when direct silent replies are explicitly allowed; native commands sent from direct chats do not borrow target-session silence.
  • Why this is the smallest reliable guardrail: runPreparedReply is the seam that computes and forwards allowEmptyAssistantReplyAsSilent into the runner.
  • Existing test that already covers this (if any): runner behavior for the flag itself is covered in src/agents/pi-embedded-runner/run.incomplete-turn.test.ts.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Operators who explicitly configure silentReply.direct: "allow" can now have clean empty or reasoning-only direct/dm model turns treated as intentional silence. Direct chats still disallow this by default.

Diagram (if applicable)

N/A

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/pnpm local checkout
  • Model/provider: N/A
  • Integration/channel (if any): direct/dm auto-reply runner
  • Relevant config (redacted): agents.defaults.silentReply.direct = "allow"

Steps

  1. Configure direct silent replies with silentReply.direct: "allow".
  2. Run a direct/dm turn where the assistant produces no visible text or only reasoning.
  3. Compare with a default direct/dm config and with a native direct command targeting a different session.

Expected

  • Explicit direct allow treats clean empty/reasoning-only as intentional silence.
  • Default direct config keeps empty-response retry/fallback behavior.
  • Targeted native commands do not inherit target-session silence for direct requester delivery.

Actual

  • Before this change, direct/dm runs never enabled empty-assistant silence because the flag was gated on isGroupChat.

Evidence

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

Human Verification (required)

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

  • Verified scenarios: explicit direct/dm allow, default direct disallow, targeted native direct command guard, existing runner handling for empty/reasoning-only silence.
  • Edge cases checked: group/channel behavior remains routed through resolveGroupSilentReplyBehavior; direct allow requires direct prompt-policy context.
  • What you did not verify: live messaging-channel delivery.

Commands run:

pnpm docs:list
pnpm test src/auto-reply/reply/get-reply-run.media-only.test.ts src/agents/pi-embedded-runner/run.incomplete-turn.test.ts
pnpm exec oxfmt --check --threads=1 src/auto-reply/reply/get-reply-run.ts src/auto-reply/reply/get-reply-run.media-only.test.ts CHANGELOG.md docs/channels/groups.md
git diff --check
pnpm changed:lanes --json
pnpm check:changed
/Users/vyctor/.codex/skills/oss-pr-contributions/scripts/public_pr_preflight.sh /Users/vyctor/Documents/Codex/2026-04-29/quero-trabalhar-na-issue-74409-do/openclaw

codex review --base origin/main initially found one P2 around native direct commands targeting another session. That finding was fixed and covered with a regression test. A final retry completed successfully with no remaining findings.

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: direct chats could accidentally suppress empty failed turns.
    • Mitigation: the direct branch requires direct prompt-policy context plus explicit silentReply.direct: "allow"; defaults remain conservative and targeted native commands are covered.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/channels/groups.md (modified, +1/-1)
  • src/auto-reply/reply/get-reply-run.media-only.test.ts (modified, +80/-1)
  • src/auto-reply/reply/get-reply-run.ts (modified, +24/-20)

Code Example

"agents": {
  "defaults": {
    "silentReply": { "direct": "allow" }
  }
}

---

const allowEmptyAssistantReplyAsSilent = isGroupChat && resolveGroupSilentReplyBehavior({
  sessionEntry,
  defaultActivation,
  silentReplyPolicy: silentReplySettings.policy,
  silentReplyRewrite: silentReplySettings.rewrite
}).allowEmptyAssistantReplyAsSilent;

---

const allowEmptyAssistantReplyAsSilent = resolveGroupSilentReplyBehavior({...}).allowEmptyAssistantReplyAsSilent;
RAW_BUFFERClick to expand / collapse

Summary

In the bundled get-reply-eY9NJdyX.js (source src/auto-reply/.../get-reply.ts), allowEmptyAssistantReplyAsSilent is gated behind isGroupChat &&, which makes it impossible to honor silentReplyPolicy: "allow" on direct (DM) chats. Empty/silent-token replies on direct chats always trigger the empty-response retry → fallback chain, even when configured to be allowed.

Repro

openclaw 2026.4.26. With:

"agents": {
  "defaults": {
    "silentReply": { "direct": "allow" }
  }
}

Send a heartbeat-style ping to a direct DM where the model would correctly respond with NO_REPLY. Observed: harness logs empty response detected → retrying with visible-answer continuation → empty response retries exhausted → incomplete turn detected stopReason=stop payloads=0 → surfacing error to user. Expected: silent absorption, since policy is "allow".

Code reference

plugin-runtime-deps/openclaw-2026.4.26-*/dist/get-reply-eY9NJdyX.js line ~2029:

const allowEmptyAssistantReplyAsSilent = isGroupChat && resolveGroupSilentReplyBehavior({
  sessionEntry,
  defaultActivation,
  silentReplyPolicy: silentReplySettings.policy,
  silentReplyRewrite: silentReplySettings.rewrite
}).allowEmptyAssistantReplyAsSilent;

The isGroupChat && short-circuit means direct chats always get false regardless of policy.

Suggested fix

Drop the isGroupChat && guard. The resolveGroupSilentReplyBehavior call's return value already encodes silentReplyPolicy === "allow" correctly (see silent-reply-Btab7kqB.js:618). The function name is a misnomer — it works for any conversation type once you let it run.

const allowEmptyAssistantReplyAsSilent = resolveGroupSilentReplyBehavior({...}).allowEmptyAssistantReplyAsSilent;

(Confirmed locally — patched the bundled JS, silentReply policy is now respected on direct chats.)

Related issues

May share root cause / overlap with:

  • #66459 — Telegram transcript contains final reply but no outbound send occurs for [thinking,text] turn
  • #72541 — Gateway completeness check false-negative on pure-relay agents
  • #73020 — Empty-response fallback leaks provider validation errors to users (closed; possibly regressed)
  • #74021 — Support reasoning-field outputs and visible final-answer handling

extent analysis

TL;DR

Remove the isGroupChat && guard from the allowEmptyAssistantReplyAsSilent assignment to respect the silentReplyPolicy for direct chats.

Guidance

  • Verify the issue by checking if the silentReplyPolicy is set to "allow" for direct chats and if the allowEmptyAssistantReplyAsSilent variable is still being set to false due to the isGroupChat && guard.
  • Remove the isGroupChat && guard from the allowEmptyAssistantReplyAsSilent assignment to allow the resolveGroupSilentReplyBehavior function to determine the correct behavior for direct chats.
  • Test the change by sending a heartbeat-style ping to a direct DM and verifying that the response is silently absorbed when the silentReplyPolicy is set to "allow".
  • Review related issues (#66459, #72541, #73020, #74021) to ensure that this change does not introduce any regressions or conflicts.

Example

const allowEmptyAssistantReplyAsSilent = resolveGroupSilentReplyBehavior({
  sessionEntry,
  defaultActivation,
  silentReplyPolicy: silentReplySettings.policy,
  silentReplyRewrite: silentReplySettings.rewrite
}).allowEmptyAssistantReplyAsSilent;

Notes

This change assumes that the resolveGroupSilentReplyBehavior function correctly handles direct chats and returns the expected value for allowEmptyAssistantReplyAsSilent. Additional testing may be necessary to ensure that this change does not introduce any unintended behavior.

Recommendation

Apply the suggested fix by removing the isGroupChat && guard, as it is a targeted change that addresses the specific issue and has been confirmed to work locally.

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…

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 silent-reply detection gated behind isGroupChat — direct chats can't honor silentReplyPolicy="allow" [3 pull requests, 1 comments, 2 participants]