fix(cron): no-deliver runs now recover from tool warnings via final assistant text#90676
fix(cron): no-deliver runs now recover from tool warnings via final assistant text#90676openperf wants to merge 1 commit into
Conversation
When deliveryPlan.mode is "none" and no channel is resolved, resolveCronChannelOutputPolicy returned preferFinalAssistantVisibleText: false. That blocked hasRecoveredToolWarning and shouldUseFinalAssistantVisibleText in resolveCronPayloadOutcome (both gates require true), so a cron run that produced valid final assistant text after only non-fatal tool warnings was still marked as a fatal error. With no channel there is no structured delivery target; the final assistant text is the only meaningful output. Returning true lets the recovery path work correctly for --no-deliver runs. Fixes openclaw#90664
|
Codex review: needs maintainer review before merge. Reviewed June 5, 2026, 9:56 AM ET / 13:56 UTC. Summary PR surface: Source +1, Tests +7. Total +8 across 2 files. Reproducibility: yes. from source inspection: bare no-deliver sets the resolved channel to Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Merge the narrow policy and regression-test fix after normal maintainer CI or requested live cron proof, leaving channel-specific output policy unchanged. Do we have a high-confidence way to reproduce the issue? Yes from source inspection: bare no-deliver sets the resolved channel to Is this the best way to solve the issue? Yes. The policy helper is the narrow shared callee for both runtime callers, and the existing helper guards still reject run-level errors, fatal failure signals, non-tool warnings, and structured delivery payloads; plumbing delivery mode through the outcome path would be broader without clear benefit. AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 520992a1defd. Label changesLabel justifications:
Evidence reviewedPR surface: Source +1, Tests +7. Total +8 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
|
Summary
--no-delivercron runs that produce only non-fatal tool warnings followed by a valid final assistant reply are incorrectly recorded as fatal errors. The run ledger shows an error payload andhasFatalErrorPayload = trueeven though the agent recovered cleanly.resolveCronChannelOutputPolicyreturns{ preferFinalAssistantVisibleText: false }whenchannelisundefined— which is exactly the valuerun.tspasses whendeliveryPlan.mode === "none"setsresolvedDelivery.channel = undefined. Both downstream recovery gates inresolveCronPayloadOutcomerequirepreferFinalAssistantVisibleText === true:hasRecoveredToolWarning(checks all error payloads are tool-warning strings plus a non-empty final text) andshouldUseFinalAssistantVisibleText. With the flagfalse,hasRecoveredToolWarningis alwaysfalse,hasFatalStructuredErrorPayloadincludes the tool warnings as fatal, and the run is markedstatus: "error"regardless of whether the agent recovered.{ preferFinalAssistantVisibleText: true }when nochannelIdis resolved. Without a channel there is no structured delivery target; the final assistant text is the only meaningful output, so preferring it is unconditionally correct for the no-channel path. The downstream guards (!runLevelError,failureSignal?.fatalForCron !== true,!hasStructuredDeliveryPayloads) remain intact and prevent false recovery for genuine errors.src/cron/isolated-agent/channel-output-policy.ts— flip the no-channel early-exit fromfalsetotrue; add a one-line comment explaining the semantic reason.src/cron/isolated-agent/channel-output-policy.test.ts— add"prefers final assistant text when no channel is resolved"test case; asserts the channel plugin runtime is not consulted (function returns before the lazy import).preferFinalAssistantVisibleText: trueor omit it still resolve exactly as before.delivery-dispatch.tsdispatch is gated onparams.deliveryRequested && …; sincedeliveryRequested: falsein the no-deliver case,deliverViaDirectis never called even thoughresolvedDeliveryPayloadsnow contains the final text. No actual channel message is sent.docs/reference/config).extensions/*api/runtime-api, registry/loader).Reproduction
--no-deliver(ordeliveryPlan.mode = "none"with no explicit target).⚠️ 🛠️ …tool-warning payloads and then produces a non-empty final assistant reply.hasFatalErrorPayload/statusfromresolveCronPayloadOutcome.preferFinalAssistantVisibleTextisfalse;hasRecoveredToolWarningis alwaysfalse;hasFatalStructuredErrorPayloadistrue; run is recorded asstatus: "error"with the tool-warning text asembeddedRunError.preferFinalAssistantVisibleTextistrue;hasRecoveredToolWarningistrue;hasFatalStructuredErrorPayloadisfalse; run is recorded asstatus: "ok"with the final assistant text asoutputText.Real behavior proof
Behavior addressed (#90664):
resolveCronChannelOutputPolicyno longer returnsfalsefor the no-channel/no-deliver path;hasRecoveredToolWarningcan now be satisfied; a no-deliver run that produces only non-fatal tool warnings followed by a final assistant reply is recorded asokwith the assistant text, not as a fatal error.Real environment tested (Linux, Node 22 — Vitest against the production
resolveCronChannelOutputPolicy,resolveCronPayloadOutcome, and the adjacent message-tool-policy helpers): the new test drivesresolveCronChannelOutputPolicy(undefined)directly and asserts that the channel plugin runtime lazy import is never triggered; the existing helpers suite confirms thatpreferFinalAssistantVisibleText: trueproduces recovery as expected.Exact steps or command run after this patch:
node scripts/run-vitest.mjs src/cron/isolated-agent/channel-output-policy.test.ts src/cron/isolated-agent.helpers.test.ts src/cron/isolated-agent/run.message-tool-policy.test.tsEvidence after fix (Vitest output for the three touched suites):
Observed result after fix:
resolveCronChannelOutputPolicy(undefined)resolves to{ preferFinalAssistantVisibleText: true }without consulting the channel plugin runtime. The existing"lets preferred final assistant text recover a plain tool warning"case in the helpers suite (which passespreferFinalAssistantVisibleText: trueexplicitly) confirms the downstream recovery path works correctly once the flag istrue.What was not tested: a live gateway cron run with
--no-deliverplus a tool-warning-emitting agent. The policy function, the recovery gate logic, and the helpers are covered deterministically against the production functions; a full gateway round-trip was not driven.Repro confirmation: the new
"prefers final assistant text when no channel is resolved"test case fails onorigin/mainbefore the patch (returnsfalse) and passes after; the mock assertion that the channel plugin runtime is never called confirms the early-exit path is exercised, not the lazy-load path.Risk / Mitigation
hasRecoveredToolWarningrequireserrorPayloads.every(payload => isCronToolWarning(payload?.text)); any non-tool-warning error payload keeps the run fatal regardless of the flag.resolvedDeliveryPayloadsnow contains the final text in no-deliver mode, which could trigger unexpected delivery. Mitigation:dispatchCronDeliveryis gated onparams.deliveryRequested && …;deliveryRequestedisfalsein the no-deliver case, sodeliverViaDirectis never called.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Fixes #90664