fix: stop repeated message_tool_only source replies#102179
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 8, 2026, 10:19 AM ET / 14:19 UTC. Summary PR surface: Source +125, Tests +376. Total +501 across 2 files. Reproducibility: yes. at source level. Current Review metrics: none identified. Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the targeted source-reply slot guard once exact-head CI and agent/channel owner review confirm it preserves follow-up tools while suppressing only duplicate implicit current-source sends. Do we have a high-confidence way to reproduce the issue? Yes, at source level. Current Is this the best way to solve the issue? Yes. The PR avoids the one-line AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against fc05a8103b72. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +125, Tests +376. Total +501 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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
Review history (5 earlier review cycles)
|
9382a3b to
e477939
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
e477939 to
aca795a
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
aca795a to
05d89d7
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@openclaw-mantis telegram desktop proof: verify that a message_tool_only group-chat run sends one visible source reply, suppresses repeated self-replies, and still allows non-message follow-up tools. |
05d89d7 to
b084883
Compare
|
Thanks for this focused fix. I have production evidence from OpenClaw 2026.7.1-beta.2 on Telegram of an adjacent duplicate path: in three duplicate pairs, the first message(action=send) returned a gateway timeout after 30s; the gateway later recorded the first Telegram send as successful about 41–46s after initiation, and the model then issued a second, paraphrased send that also succeeded. This PR correctly releases the reservation on definitive failures, but a client timeout is an ambiguous outcome: the server may still deliver after the caller gives up. In that case the reservation is released and a retry with changed text bypasses exact idempotency. I do not think that should block this PR's post-success cascade fix. Would you prefer a focused regression test/tri-state outcome here, or a separate transport-level PR that reconnects using the same idempotency key? Happy to prepare the latter and link it here; redacted timestamps are available. |
|
Follow-up: I opened #104632 as the focused companion transport fix. It is intentionally not a change request for this PR. It reconnects only after a GatewayTransportError timeout with the same idempotency key and a bounded 60-second reconciliation window, so the Gateway can join the original in-flight delivery instead of returning an ambiguous failure to the model. The regression test also proves cancellation does not reconnect. This should cover the timeout -> late success -> paraphrased duplicate path while leaving your definitive-failure reservation behavior intact. |
|
+1 from another operator. Different channel but exact same symptom. Channel: Reproduction trace from the affected session:
The session eventually drained on its own; the user had to manually intervene to stop the cascade. Re @Glucksberg's 7/11 note about an adjacent duplicate path: my trace also contains Until this lands, our workaround is a hard rule on the agent side: no Two sanity questions on the slot reservation:
cc @clawsweeper |
Closes #96827
AI-assisted: yes
What Problem This Solves
Fixes an issue where users in
message_tool_onlygroup-chat turns could see the agent send repeated visible source replies within one embedded run after the firstmessage(action="send")delivery.Why This Change Was Made
The source-reply terminal hook now reserves one implicit current-source message delivery slot per
message_tool_onlyrun. The first implicit source reply can still complete and the run can continue to non-message follow-up tools, but subsequent implicit current-sourcemessage(send)calls are returned to the model as suppressed terminal results instead of being delivered again.The slot is finalized against the
afterToolCalldelivery classification. If a hook rewrites the first raw send as failed or non-delivered, the slot is released so a later valid implicit source reply can still send. Suppressed duplicate results do not release an in-flight reservation. Explicit routes, non-message tools, dry runs, failed/non-delivered sends, and deferred message-tool hydration remain outside the suppression path.User Impact
Telegram and other
message_tool_onlysource-reply surfaces should deliver at most one visible reply to the current source conversation for a run, avoiding self-reply cascades and extra model/channel churn while preserving follow-up tool execution.Evidence
node_modules/.bin/oxfmt --write src/agents/embedded-agent-runner/run/message-tool-terminal.ts src/agents/embedded-agent-runner/run/message-tool-terminal.test.tsnode_modules/.bin/oxlint src/agents/embedded-agent-runner/run/message-tool-terminal.ts src/agents/embedded-agent-runner/run/message-tool-terminal.test.tsgit diff --checkOPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree pnpm tsgo:coreOPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=.vitest-cache-96827 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=300000 node scripts/run-vitest.mjs src/agents/embedded-agent-runner/run/message-tool-terminal.test.ts packages/agent-core/src/agent-loop.test.ts -- --reporter=verbose- 2 shards passed;message-tool-terminal.test.ts15 tests passed;agent-loop.test.ts22 tests passed.OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=.vitest-cache-96827-acceptance OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=300000 node scripts/run-vitest.mjs src/agents/embedded-agent-runner/run/message-tool-terminal.test.ts packages/agent-core/src/agent-loop.test.ts src/agents/embedded-agent-subscribe.handlers.tools.test.ts src/agents/embedded-agent-subscribe.handlers.messages.test.ts extensions/telegram/src/bot-message-dispatch.test.ts -- --reporter=verbose- 3 shards passed; Telegram dispatch file reported 171 tests passed..agents/skills/autoreview/scripts/autoreview --mode local- clean, no accepted/actionable findings; overall patch correct.OPENCLAW_BUILD_PRIVATE_QA=1 OPENCLAW_ENABLE_PRIVATE_QA_CLI=1 OPENCLAW_DISABLE_BUNDLED_PLUGINS=0 OPENCLAW_QA_SUITE_PROGRESS=1 node_modules/.bin/tsx --tsconfig tsconfig.jsoncallingrunQaSuite({ providerMode: "mock-openai", scenarioIds: ["group-visible-reply-tool"], concurrency: 1, fastMode: true, outputDir: ".artifacts/qa-e2e/source-api-group-visible-reply-tool" }).artifacts/qa-e2e/source-api-group-visible-reply-tool/qa-suite-report.md.artifacts/qa-e2e/source-api-group-visible-reply-tool/qa-suite-summary.jsongroup:qa-visible-tool-room:QA-GROUP-TOOL-OKqa/scenarios/channels/group-visible-reply-tool.yamlverifies the mock provider plannedmessage(action=send)and that the same group transcript contains exactly one outbound visible reply withQA-GROUP-TOOL-OK.Redacted local
message_tool_onlyterminal transcript from a temporarytsxharness that directly installedinstallMessageToolOnlyTerminalHookaround a message tool:{ "mode": "message_tool_only", "cascadeGuard": { "visibleSends": [ "first visible source reply" ], "visibleSendCount": 1, "duplicateSuppressed": true, "laterSuppressedAfterDelivery": true }, "hookFailureRetry": { "visibleSends": [ "hook-failed source reply", "successful retry source reply" ], "visibleSendCount": 2, "retryWasSent": true } }Additional follow-up-tool proof:
message-tool-terminal.test.tsnow includeskeeps non-message follow-up tools available after source reply delivery, which fetches thereadtool fromagent.state.toolsafterafterToolCallrecords source delivery and verifies it still executes.agent-loop.test.tsalso keeps the core loop contract covered withcontinues after a side-effect tool result when afterToolCall records it without terminate.