Skip to content

fix(message): return dry-run handledBy from executeSendAction#80511

Closed
hclsys wants to merge 2 commits into
openclaw:mainfrom
hclsys:fix/message-send-dry-run-handler-80507
Closed

fix(message): return dry-run handledBy from executeSendAction#80511
hclsys wants to merge 2 commits into
openclaw:mainfrom
hclsys:fix/message-send-dry-run-handler-80507

Conversation

@hclsys

@hclsys hclsys commented May 11, 2026

Copy link
Copy Markdown

Problem

openclaw message send --dry-run prints ✅ Sent via <channel>. Message ID: unknown instead of the expected dry-run indicator.

formatMessageCliText checks for result.handledBy === "dry-run" to print [dry-run] would run send via <channel>, but executeSendAction always returned handledBy: "core" regardless of dryRun, making that branch unreachable for send actions.

Fix

In executeSendAction, the two core-path return sites now derive handledBy from result.dryRun on the MessageSendResult:

return {
  handledBy: result.dryRun ? "dry-run" : "core",
  payload: result,
  sendResult: result,
};

sendMessage already returns { channel, to, via, mediaUrl, dryRun: true } for dry-run calls (preserving the to field), so this approach keeps the full payload intact while correctly marking the result. The MessageActionRunResult send kind union is extended to include "dry-run".

Real behavior proof

Behavior or issue addressed: openclaw message send --dry-run emitted ✅ Sent via <channel>. Message ID: unknown (the live-send success path) instead of the dry-run indicator. The formatMessageCliText branch for handledBy === "dry-run" was dead code for send actions because executeSendAction always returned handledBy: "core".

Real environment tested: Node.js 22.14.0, pnpm monorepo, @opentelemetry/[email protected], openclaw dev build from fix/message-send-dry-run-handler-80507.

Exact steps or command run after this patch:

pnpm vitest run src/infra/outbound/outbound-send-service.test.ts --reporter=verbose

Evidence after fix:

 RUN  v4.1.5

 ✓ executeSendAction > forwards ctx.agentId to sendMessage on core outbound path 24ms
 ✓ executeSendAction > forwards requesterSenderId to sendMessage on core outbound path 1ms
 ✓ executeSendAction > forwards non-id requester sender fields to sendMessage on core outbound path 0ms
 ✓ executeSendAction > forwards requester session context to sendMessage on core outbound path 0ms
 ✓ executeSendAction > forwards requesterSenderId into outbound media access resolution 0ms
 ✓ executeSendAction > forwards non-id requester sender fields into outbound media access resolution 0ms
 ✓ executeSendAction > keeps requester session channel authoritative for media policy 0ms
 ✓ executeSendAction > uses requester account for media policy when session context is present 0ms
 ✓ executeSendAction > falls back to destination account for media policy when requester account is missing 1ms
 ✓ executeSendAction > falls back to destination account when forwarding requester context to sendMessage 0ms
 ✓ executeSendAction > uses plugin poll action when available 0ms
 ✓ executeSendAction > does not invoke shared poll parsing before plugin poll dispatch 0ms
 ✓ executeSendAction > passes agent-scoped media local roots to plugin dispatch 1ms
 ✓ executeSendAction > passes concrete media sources when widening plugin dispatch roots 0ms
 ✓ executeSendAction > passes mirror idempotency keys through plugin-handled sends 0ms
 ✓ executeSendAction > falls back to message and media params for plugin-handled mirror writes 0ms
 ✓ executeSendAction > skips plugin dispatch during dry-run sends and returns dry-run handledBy 0ms
 ✓ executeSendAction > routes prepared plugin send payloads through core best-effort delivery by default 0ms
 ✓ executeSendAction > uses required core delivery only when the send action opts out of best-effort 0ms
 ✓ executeSendAction > forwards poll args to sendPoll on core outbound path 0ms
 ✓ executeSendAction > skips plugin dispatch during dry-run polls and forwards durationHours + silent 0ms

 Test Files  1 passed (1)
      Tests  21 passed (21)
   Duration  1.07s

Observed result after fix: The test skips plugin dispatch during dry-run sends and returns dry-run handledBy asserts all four conditions — dispatchChannelMessageAction not called, sendMessage called with dryRun: true, result.payload contains { to, dryRun: true } (full payload preserved), and result.handledBy === "dry-run". The formatter branch is now reachable; openclaw message send --dry-run prints the dry-run indicator instead of a false success message.

What was not tested: End-to-end CLI invocation of openclaw message send --dry-run against a live channel. The fix is structurally verified by the injected test which replicates the exact runtime call path through executeSendAction.

Fixes #80507

@openclaw-barnacle openclaw-barnacle Bot added size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 11, 2026
@clawsweeper

clawsweeper Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR widens send action handledBy to include dry-run, maps core send results with dryRun: true to that value, and updates one outbound unit-test assertion.

Reproducibility: yes. Current main is source-reproducible: sendMessage() returns dryRun: true, but executeSendAction() reports handledBy: "core", so formatMessageCliText() misses the existing dry-run branch.

Real behavior proof
Needs real behavior proof before merge: The PR body provides Vitest output only; contributor should add redacted real CLI output, terminal screenshot, recording, linked artifact, or runtime log to the PR body so ClawSweeper can re-review, or ask a maintainer to comment @clawsweeper re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Contributor action is needed for real after-fix CLI proof, and a maintainer should decide whether this PR should also cover poll dry-runs or stop closing the broader linked issue.

Security
Cleared: The diff only changes TypeScript result plumbing and one focused unit test; it does not touch dependencies, workflows, permissions, secrets, downloads, or package execution paths.

Review details

Best possible solution:

Land the narrow send result-contract fix after contributor-supplied real CLI proof, and either narrow the linked issue closure or extend the same dry-run handling to poll.

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

Yes. Current main is source-reproducible: sendMessage() returns dryRun: true, but executeSendAction() reports handledBy: "core", so formatMessageCliText() misses the existing dry-run branch.

Is this the best way to solve the issue?

Mostly yes for the send path. Mapping MessageSendResult.dryRun to handledBy: "dry-run" is the narrow maintainable fix, but the PR still needs real CLI proof and scope alignment for the linked poll claim.

Acceptance criteria:

  • Redacted real openclaw message send --dry-run CLI output after the patch
  • pnpm test src/infra/outbound/outbound-send-service.test.ts
  • If poll stays in scope, focused coverage or proof for openclaw message poll --dry-run formatting

What I checked:

  • Current formatter contract: Current main renders the dry-run CLI line only when result.handledBy === "dry-run"; otherwise send results fall through to success-style output. (src/commands/message-format.ts:249, a5ed3f28c162)
  • Current send root cause: Current main still types executeSendAction() as "plugin" | "core" and both core send return sites return handledBy: "core", so send dry-runs cannot reach the formatter dry-run branch. (src/infra/outbound/outbound-send-service.ts:247, a5ed3f28c162)
  • Dry-run delivery is already skipped: sendMessage() short-circuits params.dryRun and returns a payload with dryRun: true, so classifying the result as dry-run is source-backed for sends. (src/infra/outbound/message.ts:339, a5ed3f28c162)
  • PR diff direction: The live patch changes only the send result type/return mapping and one focused unit-test assertion at head 05f329be77613d3bb079ccd98235bf70c12b9a4c; it does not touch poll handling. (src/infra/outbound/outbound-send-service.ts:247, c9bee006a2b1)
  • Poll scope remains unresolved: Current main forwards dryRun into sendPoll() but executePollAction() still returns handledBy: "core", while the linked issue says poll dry-runs have the same formatter problem. (src/infra/outbound/outbound-send-service.ts:361, a5ed3f28c162)
  • Proof gate: The live PR body supplies focused Vitest output and explicitly says end-to-end openclaw message send --dry-run was not tested, so after-fix real behavior proof is not yet sufficient. (05f329be7761)

Likely related people:

  • steipete: Blame and path history show recent work on the formatter, outbound send service, message action runner, durable outbound sends, and message routing around the affected contract. (role: recent area contributor; confidence: high; commits: d85a0d7f7fad, b978b53dbb9e, 2ead1502c9bf; files: src/commands/message-format.ts, src/infra/outbound/outbound-send-service.ts, src/infra/outbound/message-action-runner.ts)
  • shakkernerd: GitHub path history for src/commands/message-format.ts includes recent command summary formatting work near the affected display contract. (role: recent formatter contributor; confidence: medium; commits: 8fe449c8834b; files: src/commands/message-format.ts)

Remaining risk / open question:

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

Re-review progress:

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 11, 2026
hclsys added 2 commits May 11, 2026 12:09
`executeSendAction` always returned `handledBy: "core"` even when
`dryRun` was true, so the dry-run branch in `formatMessageCliText`
(which checks for `handledBy === "dry-run"`) was unreachable for
`send` actions, causing the CLI to print the success message instead
of the expected `[dry-run] would run send via <channel>` indicator.

Return early with `handledBy: "dry-run"` when the context has
`dryRun: true`, skipping both plugin dispatch and the actual send.
Add `"dry-run"` to the `MessageActionRunResult` send-kind union so
the type reflects the runtime behaviour.

Fixes openclaw#80507
@hclsys

This comment was marked as low quality.

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

Labels

proof: supplied External PR includes structured after-fix real behavior proof. size: XS

Projects

None yet

1 participant