Skip to content

[Bug]: resolveCronChannelOutputPolicy returns preferFinalAssistantVisibleText=false when --no-deliver leaves channel unresolved, blocking hasRecoveredToolWarning rescue #90664

@dertbv

Description

@dertbv

Bug type: Behavior bug (incorrect output/state without crash)

Beta release blocker: No

Summary

resolveCronChannelOutputPolicy at dist/run-session-state-*.js:17-22 returns { preferFinalAssistantVisibleText: false } whenever no channel ID is resolved — which is the case for every --no-deliver cron — and this disables the hasRecoveredToolWarning rescue at helpers-DIfpiLk_.js:142, causing claude-cascade-style isolated dispatches to record status: error whenever their final tool call produces a ⚠️🛠️ payload, even though the agent's work succeeded and finalAssistantVisibleText is populated.

Steps to reproduce

  1. On v2026.5.28, dispatch an isolated cron with --no-deliver --expect-final --session isolated, agent prompt ending with a tool call (e.g. ~/bin/imsg send ...).
  2. Let it run to completion. Confirm via the codex rollout JSONL that the agent emitted a task_complete event and a clean agent_message final summary.
  3. Inspect openclaw cron runs --id <jobId>.

Verification dispatches in our environment (same shape, different patch states):

  • 3416705b-c0e0-48ce-9a85-5ed2f02ed576 (pre-patch): status: error, error: "⚠️ 🛠️ \show > (agent)` failed", duration 322s. task_complete` fired and two real git commits shipped (verified independently).
  • e092ae7c-553b-41d2-a094-75cafe641ae9 (post-patch, single-line dist flip described below): status: ok, no error, duration 33s, same ⚠️🛠️ payload preserved as warn severity diagnostic, summary carries agent's final text.

Expected behavior

When --no-deliver is set (deliveryPlan.mode === "none"), the agent's finalAssistantVisibleText IS the authoritative outcome (no external delivery to worry about). The hasRecoveredToolWarning rescue should fire if the only error payloads are tool warnings (text starts with ⚠️ 🛠️ ) AND a finalAssistantVisibleText is present — exactly the case present in every observed instance — yielding status: ok with the tool warning preserved as a warn-severity diagnostic.

Actual behavior

preferFinalAssistantVisibleText is false (no-channel default) → hasRecoveredToolWarning hard-gates to false → hasFatalStructuredErrorPayload is trueoutcome: error. The agent's actual final text is overwritten in the cron summary by the tool-warning text. Observed across 5+ claude-cascade-* dispatches over the past week, 3 of which were independently verified to have shipped their underlying work correctly.

OpenClaw version

2026.5.28 (e932160)

OS

macOS 26.4.1 arm64 (Darwin 25.4.0)

Install method

npm install -g openclaw under LaunchAgent (ai.openclaw.gateway, port 18789)

Model

openai-codex/gpt-5.5

Provider / routing chain

openclaw → openai-codex (OAuth) → gpt-5.5

Additional provider/model setup details

NOT_ENOUGH_INFO — bug is in cron-outcome classification, not provider chain.

Logs, screenshots, and evidence

# Pre-patch diagnostic (cron 3416705b, 2026-06-05 11:52:14Z):
status: error
error: ⚠️ 🛠️ `show > (agent)` failed
duration: 322457ms
diagnostics:
  [tool/error] ⚠️ 🛠️ `show > (agent)` failed
  [agent-run/error] ⚠️ 🛠️ `show > (agent)` failed

# Codex rollout for same cron showed (last events before turn closed):
... task_complete event fired
... agent_message: "Shipped, Bobble. Files touched: [...] Commits: 5360ee3 in ~/.openclaw, 444c27e4 in /Users/Badman/Claude..."

# Post-patch diagnostic (cron e092ae7c, 2026-06-05 12:23Z, same dispatch shape):
status: ok
error: (none)
duration: 33012ms
summary: "Line count: \`632\`. iMessage send result: \`{\"status\":\"sent\"}\`."
diagnostics:
  [tool/warn] ⚠️ 🛠️ \`~/.openclaw/scripts/imsg-angie send +17036759608 ... (agent)\` failed

Root cause (located via dist source read):

dist/run-session-state-*.js (v5.28 file is run-session-state-D6S4JhOH.js), function resolveCronChannelOutputPolicy:

async function resolveCronChannelOutputPolicy(channel) {
  const channelId = normalizeOptionalLowercaseString(channel);
  if (!channelId) return { preferFinalAssistantVisibleText: false };  // ← BUG: too-pessimistic default
  const { getChannelPlugin } = await loadChannelPluginRuntime();
  return { preferFinalAssistantVisibleText: getChannelPlugin(channelId)?.outbound?.preferFinalAssistantVisibleText === true };
}

The early-return defaults to false, but the only situations where this branch fires are (a) --no-deliver cron (explicit, user-stated that agent owns the final outcome), or (b) misconfigured cron with broken channel resolution (in which case agent text is still the only authoritative outcome). In both cases, true is the correct default — the rescue path at helpers-DIfpiLk_.js:142 was designed precisely for this scenario.

Both call sites inherit the bug:

  • dist/isolated-agent-*.js:948 (isolated-agent-6jikzXvw.js on v5.28)
  • dist/run-executor.runtime-*.js:294 (run-executor.runtime-C3DKNYjg.js on v5.28)

Proposed fix

One-line change at dist/run-session-state-*.js:19:

- if (!channelId) return { preferFinalAssistantVisibleText: false };
+ if (!channelId) return { preferFinalAssistantVisibleText: true };

A more conservative variant plumbs deliveryPlan.mode === "none" to the call site and forces true only for explicit --no-deliver, but I cannot construct a case where false is correct when there is no channel resolved — if there is no channel, the cron cannot deliver anything, so whether tool warnings are recoverable should depend on the agent's final text (the rescue's design intent), not on a channel policy that does not exist.

Local mitigation deployed and verified in our environment as described above. Happy to open a PR if useful.

Impact and severity

  • Affected: Anyone running isolated cron jobs with delivery.mode: "none" (--no-deliver) where the agent's final step is a tool call. Includes managed Memory Dreaming Promotion (uses delivery: { mode: "none" }), any claude-cascade-*-style ad-hoc dispatches, and any pattern where an agent owns its own delivery in-loop.
  • Severity: Medium. No data loss; agent work actually succeeds. But cron status: error becomes unreliable as a monitoring signal — every successful cascade dispatch trips downstream cron_failures HIGH alerts after 3 consecutive misclassifications, and "did the cron actually work?" requires rollout-file forensics every time instead of trusting the status field.
  • Frequency: Always — 5/5 observed claude-cascade-* dispatches over the past week.
  • Consequence: False-positive monitoring alerts; operator time spent verifying success that should be trusted from the status field; loss of the cron status field's signal value.

Additional information

Related but distinct branches of the same function:

Both threads cover different branches; this report covers the no-channel default specifically.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions