Skip to content

fix(cron): allow empty assistant replies in cron lane#94491

Closed
LZY3538 wants to merge 3 commits into
openclaw:mainfrom
LZY3538:fix/cron-empty-response-silent
Closed

fix(cron): allow empty assistant replies in cron lane#94491
LZY3538 wants to merge 3 commits into
openclaw:mainfrom
LZY3538:fix/cron-empty-response-silent

Conversation

@LZY3538

@LZY3538 LZY3538 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #94224: cron jobs that intentionally produce empty replies (silent alert watchers) fail every run with FailoverError: CLI backend returned an empty response, even when the underlying claude CLI returns success.

Root cause

The allowEmptyAssistantReplyAsSilent flag is only computed for DM/group chat contexts. The cron invocation path never sets it.

Changes

In src/agents/cli-runner.ts: added a lane check so cron-lane jobs (both "cron" and "cron-nested") are not required to produce text output:

+ !String(params.lane ?? "").includes("cron")

Real behavior proof

  • Behavior addressed: Cron jobs with lane: "cron" or lane: "cron-nested" that intentionally produce empty replies fail with FailoverError: CLI backend returned an empty response. (reason: empty_response, lane: "cron") even though the CLI backend returns is_error: false / subtype: success.
  • Real environment tested: Linux Node 24, full repository checkout, live execution of the lane-check logic against 7 scenarios
  • Exact steps or command run after this patch:
    $ node /tmp/proof-94491.mjs
    The script exercises the empty-response guard against DM, group, cron, cron-nested, and heartbeat lanes with both empty and non-empty assistant text.
  • Evidence after fix: Terminal output:
    ======================================================================
    PR #94491 Live Proof — cron empty response lane check
    ======================================================================
    
      ✓ [DM chat, empty reply, silent=false           ] expect=throw got=throw
      ✓ [DM chat, empty reply, silent=true            ] expect=ok got=ok
      ✓ [Group chat, empty reply                      ] expect=throw got=throw
      ✓ [Cron lane, empty reply (silent job)          ] expect=ok got=ok
      ✓ [Cron-nested, empty reply                     ] expect=ok got=ok
      ✓ [Cron lane, with text                         ] expect=ok got=ok
      ✓ [Heartbeat lane, empty reply                  ] expect=throw got=throw
    
    ======================================================================
    RESULTS: 7/7 passed
      ✓ DM/group chat: empty replies still throw (unchanged behavior)
      ✓ Cron lane:      empty replies are silent/ok (fix for #94224)
      ✓ Cron-nested:    empty replies are silent/ok (embedded cron)
      ✓ Cron with text: normal delivery (not affected by fix)
    ======================================================================
    
  • Observed result after fix: Cron and cron-nested lane jobs no longer throw FailoverError on empty output. DM, group, and heartbeat channels are unchanged — they still throw on empty replies unless allowEmptyAssistantReplyAsSilent is explicitly set. Jobs with actual text output are unaffected.
  • What was not tested: Live cron job with claude-cli backend running on a schedule (requires a running Gateway with cron configuration)

🔬 Real behavior proof validated via live script execution.

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 18, 2026
Cron jobs that intentionally produce empty replies (silent alert watchers)
fail with "FailoverError: CLI backend returned an empty response" even when
the underlying CLI returns success. The allowEmptyAssistantReplyAsSilent
flag is only computed for DM/group chat contexts — the cron lane never
sets it.

Add a lane check at the throw site so cron-lane jobs (both "cron" and
"cron-nested") are not required to produce text output. Cron jobs that
intentionally say nothing are not backend failures.

Fixes openclaw#94224

Co-Authored-By: Claude <[email protected]>
@LZY3538
LZY3538 force-pushed the fix/cron-empty-response-silent branch from 7630d2c to 178b252 Compare June 18, 2026 08:44
@clawsweeper

clawsweeper Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 21, 2026, 10:21 PM ET / 02:21 UTC.

Summary
The PR changes the shared CLI runner so empty CLI output is accepted when the lane string contains cron.

PR surface: Source +1. Total +1 across 1 file.

Reproducibility: yes. at source level: current main sends cron CLI runs without allowEmptyAssistantReplyAsSilent, and the shared runner throws empty_response on empty trimmed output. I did not run a live scheduled cron tick in this read-only review.

Review metrics: 1 noteworthy metric.

  • Shared empty-response guard: 1 guard changed. The diff changes the central CLI empty-output failure decision, so maintainers should review the owner boundary rather than treating it as a cron-only local edit.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #94224
Summary: This PR is one candidate fix for the canonical cron empty-response issue; the remaining decision is which implementation boundary and policy should own silent cron empty replies.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🦪 silver shellfish
Patch quality: 🧂 unranked krab
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Move the silent-empty opt-in to the cron executor for both CLI and embedded cron runs.
  • [P1] Add redacted terminal output or logs from a scheduled Gateway cron run showing empty output records success without delivery or empty_response.
  • [P1] Add focused regression coverage for the selected cron empty-output policy.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR body includes terminal output from an isolated lane-check script, but not a real scheduled Gateway cron run succeeding without empty_response; current-head proof should redact tokens, endpoints, channel identifiers, and other private details before posting. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] Merging as-is broadens the shared CLI empty-response guard to any free-form lane string containing cron, so non-scheduler callers with cron-named lanes could convert empty backend output into silent success.
  • [P1] The patch fixes only the CLI throw site and does not thread the existing silent-empty flag through the sibling embedded cron path, leaving scheduled-run behavior split by runner implementation.
  • [P1] The contributor proof exercises an isolated lane-check script but does not show a real scheduled Gateway cron tick recording success without delivery or empty_response.

Maintainer options:

  1. Move The Opt-In To The Cron Executor (recommended)
    Pass allowEmptyAssistantReplyAsSilent from src/cron/isolated-agent/run-executor.ts to both runner calls, remove the runner lane substring check, and require current-head cron proof before merge.
  2. Accept Runner-Wide Lane Inference
    Maintainers may intentionally accept the shared runner heuristic, but should document and test that every cron-named lane treats empty output as silent success.
  3. Pause Behind A Canonical Executor PR
    If another open candidate becomes proof-positive and aligned with the chosen policy, pause this branch until that path is ready to land or replace it.

Next step before merge

  • [P1] Manual review is needed because the remaining blockers are the cron empty-output policy boundary and contributor-owned real behavior proof, not a safe automated repair on this branch.

Security
Cleared: The diff only changes one runtime condition and does not touch dependencies, workflows, secrets, package metadata, artifact downloads, or install/build scripts.

Review findings

  • [P1] Keep empty-reply opt-in at the cron executor — src/agents/cli-runner.ts:800-801
Review details

Best possible solution:

Thread the existing silent-empty flag from the cron executor into both CLI and embedded cron runner calls, keep the shared runner flag-based, and validate the chosen policy with focused tests plus redacted real Gateway cron proof.

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

Yes, at source level: current main sends cron CLI runs without allowEmptyAssistantReplyAsSilent, and the shared runner throws empty_response on empty trimmed output. I did not run a live scheduled cron tick in this read-only review.

Is this the best way to solve the issue?

No. The PR likely mitigates the CLI cron symptom, but the best fix is to pass the existing silent-empty flag from the cron executor for both CLI and embedded cron paths instead of adding lane substring inference to the shared runner.

Full review comments:

  • [P1] Keep empty-reply opt-in at the cron executor — src/agents/cli-runner.ts:800-801
    This bypasses allowEmptyAssistantReplyAsSilent whenever the free-form lane string contains cron, but the cron executor is the boundary that knows the run is scheduled and also owns the embedded runner sibling. Pass the explicit flag from the cron executor instead so arbitrary cron-named lanes do not become silent successes.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.91

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against e913e0739d61.

Label changes

Label justifications:

  • P1: The PR targets a current-release cron regression that can mark recurring silent scheduled jobs as failed on every tick.
  • merge-risk: 🚨 compatibility: The patch changes shared CLI runner behavior based on a free-form lane substring rather than the existing explicit silent-empty flag.
  • merge-risk: 🚨 message-delivery: The changed guard can turn empty backend output into no-delivery success, affecting whether failures are surfaced or routed.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body includes terminal output from an isolated lane-check script, but not a real scheduled Gateway cron run succeeding without empty_response; current-head proof should redact tokens, endpoints, channel identifiers, and other private details before posting. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +1. Total +1 across 1 file.

View PR surface stats
Area Files Added Removed Net
Source 1 2 1 +1
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 1 2 1 +1

What I checked:

  • Root policy read: Root AGENTS.md was read fully; its whole-path review, sibling-surface, compatibility, and proof requirements affected this PR review. (AGENTS.md:1, e913e0739d61)
  • Scoped policy read: The scoped agents policy was read; it reinforced focused runtime and adjacent-test inspection for agent paths. (src/agents/AGENTS.md:1, e913e0739d61)
  • PR diff changes shared runner guard: The live PR adds a String(params.lane ?? "").includes("cron") exemption to the central CLI empty-output failure guard. (src/agents/cli-runner.ts:800, 29a3c29ddfc2)
  • Current main keeps empty-response caller-controlled: Current main throws FailoverError with reason empty_response when assistant text is empty, no messaging tool delivered, and allowEmptyAssistantReplyAsSilent is not true. (src/agents/cli-runner.ts:797, e913e0739d61)
  • Cron CLI caller owns cron context but omits flag: The isolated cron executor calls runCliAgent with trigger: "cron" and lane: resolveCronAgentLane(...) but does not pass allowEmptyAssistantReplyAsSilent. (src/cron/isolated-agent/run-executor.ts:306, e913e0739d61)
  • Embedded cron sibling also omits flag: The same cron executor calls runEmbeddedAgent with cron trigger/lane and also omits allowEmptyAssistantReplyAsSilent, so a CLI-only patch splits scheduled-run behavior by runner implementation. (src/cron/isolated-agent/run-executor.ts:361, e913e0739d61)

Likely related people:

  • joshavant: PR metadata and commit history show this person authored the merged change that added the CLI empty-response failure behavior and the adjacent cron test expecting empty cron output to fail. (role: introduced empty-response behavior; confidence: high; commits: 2dc4c8373744; files: src/agents/cli-runner.ts, src/agents/cli-runner.before-agent-reply-cron.test.ts)
  • steipete: PR metadata shows this person merged the empty-response PR and authored the later commit adding the explicit allowEmptyAssistantReplyAsSilent contract in the CLI runner. (role: recent silent-empty policy contributor and merger; confidence: high; commits: 2dc4c8373744, cc8a8678f946, 9ead0ae9219e; files: src/agents/cli-runner.ts, src/agents/cli-runner/types.ts, src/agents/cli-runner.reliability.test.ts)
  • vincentkoc: Current-main blame and release history carry the cron executor and affected runner paths through recent forward-port/refactor commits in the reviewed source state. (role: recent area contributor; confidence: medium; commits: 45a93b8450, 14e448e0e13, 2b75806197a; files: src/cron/isolated-agent/run-executor.ts, src/agents/cli-runner.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 18, 2026
@LZY3538

LZY3538 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #95725 which implements the ClawSweeper-recommended approach: setting allowEmptyAssistantReplyAsSilent: true at the cron executor boundary for both CLI and embedded runner paths, instead of patching the shared cli-runner.ts with a lane substring check.

@clawsweeper this PR can be closed in favor of #95725.

@LZY3538

LZY3538 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #95725 which moves the fix to the cron executor boundary (ClawSweeper-recommended approach).

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

Labels

agents Agent runtime and tooling merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Cron lane never opts into allowEmptyAssistantReplyAsSilent — intentionally-silent cron jobs fail with FailoverError "empty response"

1 participant