Skip to content

fix(auto-reply): avoid silent completion when dispatch produces no sendable reply#63025

Closed
Leon-llb wants to merge 5 commits into
openclaw:mainfrom
Leon-llb:codex/fix-empty-run-fallback
Closed

fix(auto-reply): avoid silent completion when dispatch produces no sendable reply#63025
Leon-llb wants to merge 5 commits into
openclaw:mainfrom
Leon-llb:codex/fix-empty-run-fallback

Conversation

@Leon-llb

@Leon-llb Leon-llb commented Apr 8, 2026

Copy link
Copy Markdown

Summary

This fixes a silent completion path in dispatchReplyFromConfig.

In the current flow, a normal user message can make it through dispatch and still end with no sendable output, leaving the user with no reply and no visible error. In logs this shows up as dispatch complete (queuedFinal=false, replies=0).

What Changed

  • add a narrow fallback final reply when a normal embedded dispatch produces no sendable final, tool, or block output
  • keep existing intentional silent branches unchanged
  • add a regression test for the empty-output path

Scope

This PR fixes the "silent no-reply" behavior only.

It does not change search/tool execution behavior, and it does not solve cases where a long-running task times out after repeated upstream tool failures.

Testing

Ran:

  • ./node_modules/.bin/vitest run --config vitest.config.ts src/auto-reply/reply/dispatch-from-config.test.ts -t "sends a fallback final reply when model dispatch produces no sendable output"
  • ./node_modules/.bin/vitest run --config vitest.config.ts src/auto-reply/reply/dispatch-from-config.test.ts -t "silently short-circuits when hook returns handled without text"

Notes

AI-assisted: yes

在普通消息分发无可发送输出时补发错误提示
减少 replies=0 导致的用户侧静默无响应
保留显式静默分支与现有 hook 行为
@greptile-apps

greptile-apps Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a narrow fallback final reply in dispatchReplyFromConfig for the case where an embedded (non-routed) model dispatch completes with zero sendable final, tool, or block outputs, preventing silent no-reply UX. The condition is correctly guarded to be mutually exclusive with the TTS block-streaming path and intentional silent branches (before_dispatch handled without text, plugin binding short-circuits, etc.).

Confidence Score: 5/5

Safe to merge — the fallback is narrowly scoped, mutually exclusive with existing silent branches, and covered by regression tests.

All findings are P2. The logic is correct: the fallback condition guards properly with !shouldRouteToOriginating, blockCount === 0, counts.tool === 0, counts.block === 0, and counts.final === 0, making it mutually exclusive with the TTS block-streaming path and every intentional early-return. No P0/P1 issues found.

No files require special attention.

Vulnerabilities

No security concerns identified. The fallback path only emits a static error string and does not expose internal state or user data.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/dispatch-from-config.ts
Line: 85-86

Comment:
**Fallback message may mislead when no tools were invoked**

`EMPTY_RUN_FALLBACK_TEXT` says "A tool may have failed, timed out, or been blocked", but the fallback fires when `counts.tool === 0` (no tool results were dispatched). In the zero-tool case the message slightly misattributes the empty output. Consider a more neutral phrasing like "I couldn't produce a reply for that request. Please try again or rephrase your message."

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(auto-reply): 避免空回复时静默结束" | Re-trigger Greptile

Comment on lines +85 to +86
const EMPTY_RUN_FALLBACK_TEXT =
"I couldn't produce a sendable reply for that request. A tool may have failed, timed out, or been blocked. Please retry with a narrower task or a different source.";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fallback message may mislead when no tools were invoked

EMPTY_RUN_FALLBACK_TEXT says "A tool may have failed, timed out, or been blocked", but the fallback fires when counts.tool === 0 (no tool results were dispatched). In the zero-tool case the message slightly misattributes the empty output. Consider a more neutral phrasing like "I couldn't produce a reply for that request. Please try again or rephrase your message."

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/dispatch-from-config.ts
Line: 85-86

Comment:
**Fallback message may mislead when no tools were invoked**

`EMPTY_RUN_FALLBACK_TEXT` says "A tool may have failed, timed out, or been blocked", but the fallback fires when `counts.tool === 0` (no tool results were dispatched). In the zero-tool case the message slightly misattributes the empty output. Consider a more neutral phrasing like "I couldn't produce a reply for that request. Please try again or rephrase your message."

How can I resolve this? If you propose a fix, please make it concise.

Leon-llb added 4 commits April 8, 2026 16:24
避免在无工具输出场景中误导性归因
同步更新回归测试断言
合并 origin/main 并保留空回复兜底修复
同时保留主干新的媒体类型规范化实现
避免通用 fallback 误伤 Slack 无回复也合法的 ack 场景
仅在 direct/private 类会话且确实无可发送输出时补发提示
补充频道消息回归测试,保持现有静默分支行为不变
@Leon-llb

Leon-llb commented Apr 9, 2026

Copy link
Copy Markdown
Author

Addressed the CI regression by narrowing the fallback to direct/private chat contexts only. All checks are now passing.

@clawsweeper

clawsweeper Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and the useful part of this older PR is already implemented there.

Close this older PR as mostly implemented on main: the useful no-visible-reply fallback work is now covered by a safer policy-aware eligibility signal and Feishu delivery fallback shipped in v2026.6.6, while remaining cross-channel semantics are tracked by the canonical maintainer issue.

So I’m closing this older PR as already covered on main rather than keeping a mostly-duplicated branch open.

Review details

Best possible solution:

Keep the shipped v2026.6.6 policy-aware fallback eligibility and Feishu delivery path, and use #87561 for remaining cross-channel contract work.

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

No high-confidence current-main failing reproduction remains for the central shipped Feishu no-visible path. Source and release inspection show current main/v2026.6.6 returns fallback eligibility and sends the sanitized Feishu fallback while preserving intentional silence and suppression policy.

Is this the best way to solve the issue?

No, this stale branch is no longer the best way to solve the issue. Current main uses a narrower channel-owned fallback path, and remaining cross-channel behavior belongs in the canonical durable delivery contract issue.

Security review:

Security review needs attention: The stale diff is security/policy-sensitive because it can send a fallback outside current source-reply suppression boundaries.

  • [medium] Preserve source-reply suppression before fallback sends — src/auto-reply/reply/dispatch-from-config.ts:1048
    The PR adds a fallback send without checking the current sendPolicyDenied or message_tool_only suppression state, so a visible reply can be emitted where current policy says source delivery should stay private or suppressed.
    Confidence: 0.84

AGENTS.md: found and applied where relevant.

What I checked:

Likely related people:

  • ArthurNie: Authored the merged Feishu no-visible-reply fallback PR that introduced the current fallback eligibility and channel delivery implementation. (role: implemented superseding behavior; confidence: high; commits: 7c15c2765ebb; files: src/auto-reply/reply/dispatch-from-config.ts, extensions/feishu/src/bot.ts, extensions/feishu/src/reply-dispatcher.ts)
  • steipete: Merged the superseding fallback PR according to live PR metadata, making them a useful routing candidate for the shipped behavior decision. (role: merger and recent area reviewer; confidence: high; commits: 7c15c2765ebb; files: src/auto-reply/reply/dispatch-from-config.ts, extensions/feishu/src/bot.ts, extensions/feishu/src/reply-dispatcher.ts)
  • Ted Li: Recent source-reply delivery work defines when plugin-owned or message-tool-only replies are visible, which is central to avoiding fallback delivery policy bypasses. (role: adjacent source-reply policy contributor; confidence: medium; commits: 92b6af76d9f6; files: src/auto-reply/reply/source-reply-delivery-mode.ts, src/auto-reply/reply/dispatch-from-config.ts)
  • max: Introduced the ChatType normalization contract that maps dm to direct and does not include the PR branch's local private alias. (role: introduced related chat-type contract; confidence: medium; commits: 223eee0a20c4; files: src/channels/chat-type.ts)

Codex review notes: model internal, reasoning high; reviewed against e7ee1c55b433; fix evidence: release v2026.6.6, commit 7c15c2765ebb.

@clawsweeper

clawsweeper Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

What this changes:

The PR adds a static error fallback in dispatchReplyFromConfig for direct/private embedded dispatches that produce no final, tool, or block output, plus regression tests for the fallback and channel no-op path.

Maintainer follow-up before merge:

This is an open implementation PR with a valid current-main gap, but the remaining action is maintainer review of delivery-policy and chat-type semantics rather than an automated replacement fix.

Review details

Best possible solution:

Land a narrow dispatch-level empty-output fallback only after final queued counts are known, guarded by delivery policy, direct-style conversation policy, no routed-origin delivery, no queued final/tool/block output, no block-stream/TTS output, and no intentional handled/no-text branch. Use normalizeChatType unless maintainers intentionally add private as a supported alias with tests.

Acceptance criteria:

  • pnpm test src/auto-reply/reply/dispatch-from-config.test.ts
  • pnpm test src/channels/channels-misc.test.ts

What I checked:

  • Current main still silently completes zero-output dispatch: On current main, an undefined resolver result becomes replies = []; after the guarded delivery block, dispatch reads queued counts, records completed, marks idle, and returns without queuing any fallback when final/tool/block counts are all zero. (src/auto-reply/reply/dispatch-from-config.ts:1306, acae48b790fa)
  • Delivery suppression is an explicit contract: Current main resolves sendPolicy and source reply visibility before dispatch; sendPolicy: deny or message_tool_only sets suppressDelivery, and normal final delivery is gated by if (!suppressDelivery). (src/auto-reply/reply/source-reply-delivery-mode.ts:56, acae48b790fa)
  • Existing tests protect no-delivery behavior: Current tests assert that sendPolicy: deny still processes the turn but suppresses final, tool, and block delivery, and that a handled before_dispatch hook without text remains intentionally silent. (src/auto-reply/reply/dispatch-from-config.test.ts:3866, acae48b790fa)
  • Direct versus group/channel policy supports a narrow fallback: The user-facing message docs say direct conversations disallow silence by default, while groups/channels and internal orchestration allow silence by default. Public docs: docs/concepts/messages.md. (docs/concepts/messages.md:174, acae48b790fa)
  • Canonical chat type contract does not include private: normalizeChatType currently normalizes direct and legacy dm to direct, accepts group and channel, and returns undefined for unknown values; the PR's direct/private helper would need deliberate contract alignment if private is meant to be supported. (src/channels/chat-type.ts:3, acae48b790fa)
  • Security review scope: The PR changes only src/auto-reply/reply/dispatch-from-config.ts and its test file, with no workflow, dependency, lockfile, release, or package-publishing files. The security-relevant concern is policy bypass if a fallback is sent when delivery is explicitly suppressed. (7b583d2f5c02)

Likely related people:

  • @steipete: Recent commits in the dispatch and source-reply-delivery path centralize visibility policy and visible reply behavior, including 67b16a4a6d25ea31a4cc021c3663b26aa2a6c34f and e1fd27fb24ae81e27cf4ac1297410491009a70c0. (role: recent maintainer; confidence: high; commits: 67b16a4a6d25, 249cb5437364, e1fd27fb24ae; files: src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/reply/source-reply-delivery-mode.ts, src/auto-reply/reply/dispatch-from-config.test.ts)
  • @scoootscooob: Authored the recent group/channel message-tool default change that defines where automatic source replies should stay suppressed, which is central to safely scoping this fallback. (role: adjacent recent owner; confidence: medium; commits: 3c636208b0a0; files: src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/reply/dispatch-from-config.test.ts)
  • @quotentiroler: Introduced the ChatType direct/dm normalization work that the PR's local direct|dm|private helper would need to align with or deliberately extend. (role: introduced related contract; confidence: medium; commits: 223eee0a20c4; files: src/channels/chat-type.ts)

Remaining risk / open question:

  • The PR fallback condition shown in the provided diff is outside the existing !suppressDelivery delivery block and does not explicitly check suppressDelivery, so a direct session with sendPolicy: deny could receive an outbound fallback unless the branch is updated.
  • The PR's local direct|dm|private helper may diverge from normalizeChatType, whose canonical contract currently excludes private.
  • Current main has changed source-reply delivery behavior since the PR base, so the branch needs rebase/review against the current message_tool_only group/channel policy and broader suppressed/no-op coverage.

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

@clawsweeper clawsweeper Bot added the mantis: telegram-visible-proof Mantis should capture Telegram visible proof. label May 13, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 13, 2026
@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. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels May 19, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 5, 2026
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. 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. labels Jun 5, 2026
@clawsweeper clawsweeper Bot removed the mantis: telegram-visible-proof Mantis should capture Telegram visible proof. label Jun 5, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Jun 6, 2026
@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. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. and removed rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mantis: telegram-visible-proof Mantis should capture Telegram visible proof. 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. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant