Skip to content

Bug: isolated agentTurn cron marked error when exec exits 1, even when agent recovers and produces correct output #94863

Description

@redasadki

Bug: isolated agentTurn cron jobs fail when exec tool returns exit code 1, even when the agent recovers and produces correct output

Version: 2026.6.8 (844f405)
Component: Cron / isolated agentTurn runner


Summary

An isolated agentTurn cron job is marked as status: "error" when an exec tool call returns a non-zero exit code, even when:

  • The session itself completes with finalStatus: "success"
  • The agent successfully interprets the exit code as a signal (not an error), produces correct output, and the run is functionally correct
  • The cron prompt explicitly instructs the agent to treat exit 1 as a meaningful result

This breaks the established pattern of using exit 1 as a "work to do" signal in cron scripts that the agent then acts on.


Reproduction

Configure an agentTurn cron job whose prompt instructs:

Run: python3 /path/to/check-script.py
If exit 0: log OK.
If exit 1: include the stdout output in your reply (it contains an update command).

The script exits 1 when it has something to report (e.g. an upstream update is available). The agent reads the output and produces a correct, useful reply. The trajectory shows finalStatus: "success" and the agent's assistantTexts contain the expected output.

Expected: cron.state.lastRunStatus = "ok"
Actual: cron.state.lastRunStatus = "error" with lastError: "⚠️ 🛠️ \run python3 /path/to/check-script.py` failed"`


Evidence from trajectory

Trajectory for a real failing run (f5ec7832-f63c-47a8-9a15-7f4351240a91):

// session.completed event
{
  "type": "session.completed",
  "data": {
    "finalStatus": "success",
    "aborted": false,
    "timedOut": false,
    "assistantTexts": [
      "I'll check for upstream updates to both Superpowers and last30days.",
      "**Upstream Updates Summary:**\n\n**Superpowers v6.0.3 available** ..."
    ],
    "lastToolError": {
      "toolName": "exec",
      "meta": "run python3 ~/.openclaw/workspace/skills/superpowers-sync/scripts/superpowers-update-check.py",
      "error": ":zap: *obra/superpowers* has a new release: *v6.0.3* (`v6.0.3`)",
      "mutatingAction": true,
      "actionFingerprint": "..."
    }
  }
}

Despite finalStatus: "success" and correct assistantTexts, the cron runtime surfaces lastToolError as the job-level error.


Root cause (traced through source)

In helpers-CYQZyDV5.js, resolveCronPayloadOutcome() determines hasFatalErrorPayload:

const hasFatalStructuredErrorPayload =
  hasErrorPayload &&
  !hasSuccessfulPayloadAfterLastError &&
  !hasPendingPresentationWarning &&
  !hasNonTerminalToolErrorWarning &&
  !hasRecoveredToolWarning;

The hasRecoveredToolWarning escape hatch requires both:

  1. isCronToolWarning(payload.text) → true (the ⚠️ 🛠️ prefix matches ✅)
  2. preferFinalAssistantVisibleText === true (from resolveCronChannelOutputPolicy)

The problem is condition 2. preferFinalAssistantVisibleText is only true when the delivery channel plugin sets outbound.preferFinalAssistantVisibleText = true. For Slack, this appears to be false (or absent), so the escape hatch never fires. The tool error becomes fatal regardless of whether the agent recovered.

The result: a cron job where the agent correctly handles a non-zero exit code and produces valid output is nonetheless marked as failed, fires a failureAlert, and increments consecutiveErrors.


Impact

  • Affects any isolated agentTurn cron that runs a script using exit 1 as a "signal" rather than an "error"
  • Common pattern in script-driven crons: exit 0 = nothing to do, exit 1 = work to report, exit 2 = send a notification
  • The cron fires correct Slack notifications via the agent's text reply (via delivery.mode: "announce"), while simultaneously being marked as error and triggering failureAlert noise
  • consecutiveErrors increments on every successful run, eventually triggering alerts after N errors even though nothing is broken

Affected real-world jobs in our setup: upstream-update-checks-12h (9 consecutive false errors), daily-ap-followup (3 false errors), token-watchdog (1 false error).


Expected behavior

If the agent successfully produces output in response to a tool's non-zero exit (i.e. finalStatus: "success", non-empty assistantTexts, and the error payload is a ⚠️ 🛠️-prefixed tool warning), the cron run should be classified as ok, not error.

Specifically: hasRecoveredToolWarning should not require preferFinalAssistantVisibleText to be set by the channel plugin. When finalAssistantVisibleText exists and all error payloads pass isCronToolWarning(), the warning should be treated as non-fatal regardless of delivery channel.


Workaround

Currently none clean. Possible awkward workarounds:

  • Rewrite all scripts to use only exit 0 (no output) and exit 2 (notify), never exit 1 — but this conflicts with standard Unix convention and requires rewriting many existing scripts
  • Redirect exit-1 text to stderr (not picked up as an error payload) — brittle

Environment

  • macOS 26.5.1 (arm64), Node v24.16.0
  • OpenClaw 2026.6.8 (844f405)
  • Delivery channel: Slack (mode: "announce", channel: "slack")
  • Session target: isolated
  • Payload kind: agentTurn
  • Model: anthropic/claude-haiku-4-5-20251001

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions