Skip to content

fix(cron): keep no-channel implicit cron runs successful instead of failing delivery (#56078)#91399

Closed
MoerAI wants to merge 5 commits into
openclaw:mainfrom
MoerAI:fix/cron-no-channel-output
Closed

fix(cron): keep no-channel implicit cron runs successful instead of failing delivery (#56078)#91399
MoerAI wants to merge 5 commits into
openclaw:mainfrom
MoerAI:fix/cron-no-channel-output

Conversation

@MoerAI

@MoerAI MoerAI commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Cron agentTurn jobs with no configured channel could run the agent successfully but still mark the whole cron run as failed because implicit delivery to last could not resolve a channel. Users saw a failed cron despite the useful work already completing.

Evidence

Focused proof from this maintenance cycle: node scripts/run-vitest.mjs src/cron/isolated-agent/run.message-tool-policy.test.ts passed, targeted oxfmt passed, pnpm tsgo passed with only the repo Node engine warning, git diff --check passed, and LSP diagnostics were clean for src/cron/isolated-agent/run.ts and src/cron/isolated-agent/run.message-tool-policy.test.ts. The fix was committed as 69e0f54fb5 and pushed.


Summary

On a local / no-channel setup, backend- or Control-UI-created isolated agentTurn cron jobs fail their whole run with Channel is required (no configured channels detected) even though the agent turn ran successfully (e.g. it wrote a report file). The run is marked status:"error" and the user sees no output. This patch keeps such runs successful: an implicit announce-to-last delivery that cannot resolve a channel now finishes as a silent ok (delivered:false) instead of hard-failing, while explicit delivery targets still fail-closed.

Root Cause

  • src/cron/delivery-plan.ts:resolveCronDeliveryPlan: isolated/current/session agentTurn|command jobs with no explicit delivery config default to mode:"announce", channel:"last", requested:true.
  • src/cron/isolated-agent/delivery-target.ts:resolveDeliveryTarget (≈249-260): with no previously-used channel, returns { ok:false, error:"Channel is required when delivery.channel=last has no previous channel." }.
  • src/cron/isolated-agent/delivery-dispatch.ts:dispatchCronDelivery (the if (deliveryRequested && !skipHeartbeatDelivery && !sourceDeliverySatisfied) { if (!resolvedDelivery.ok) { if (!deliveryBestEffort) ... } } branch): for the non-best-effort default it called failDeliveryTarget(...)status:"error", errorKind:"delivery-target". Execution path: run.ts dispatchCronDelivery -> resolvedDelivery.ok===false -> failDeliveryTarget.
  • clawsweeper direction on [Bug]: Cron Job Output Not Saved in OpenClaw 2026.3.24 #56078: "current main and the latest release still default Control UI and backend-created isolated agent-turn cron jobs to strict announce delivery against last, so a local/no-channel setup can still hit the reported Channel is required delivery-target failure."

An implicit announce-to-last with no resolvable channel never had a sendable target, so hard-erroring delivers nothing — it only marks a successful turn as failed and drops its output.

Changes

  • src/cron/isolated-agent/delivery-dispatch.ts: add deliveryTargetExplicit to DispatchCronDeliveryParams; gate the unresolved-target hard-fail on it — if (deliveryTargetExplicit && !deliveryBestEffort) still calls failDeliveryTarget, otherwise it takes the existing silent-ok path (logCronDeliveryWarn + status:"ok", delivered:false, deliveryAttempted:false), the same graceful path best-effort delivery already uses.
  • src/cron/isolated-agent/run.ts: pass deliveryTargetExplicit: hasExplicitCronDeliveryTarget(prepared.deliveryPlan) (already-imported helper; delivery-plan.ts:25-29 returns true only for a concrete channel!=="last" / to / threadId / accountId).
  • src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts: regression coverage for both sides.

Reproduction (before fix)

FAIL  downgrades an unresolved implicit last-channel delivery to a silent ok (#56078)
AssertionError: status: expected 'error' to deeply equal 'ok'
  at expectResultFields (delivery-dispatch.double-announce.test.ts:282)
Test Files  1 failed | ... ; Tests  1 failed | 69 passed (70)

Verification (after fix)

Tests  70 passed (70)
  ✓ downgrades an unresolved implicit last-channel delivery to a silent ok (#56078)
  ✓ still fails the run when an explicit delivery target cannot be resolved (#56078 guard)

Test

  • pnpm test src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts — 70 passed (was 69/70 RED before the one-line gate; 68 pre-existing cases unchanged)
  • pnpm tsgo:core — passed (clean)

Notes

  • Scope: single issue, smallest complete fix — one gated condition + one threaded boolean. ~70 LOC across 3 files (2 prod + 1 test).
  • No message loss: only IMPLICIT announce-to-last with an unresolvable channel is downgraded; jobs that named a concrete channel/recipient/thread/account still fail-closed exactly as before. This mirrors the already-shipped best-effort branch.
  • Discriminator: hasExplicitCronDeliveryTarget(deliveryPlan), not resolvedDelivery.modemode only tracks an explicit to, so an explicit channel-only job would otherwise be wrongly downgraded.
  • Downstream: status:"ok" + delivered:false records not-delivered and intentionally stops retry/backoff/error alerts for the no-channel default; explicit targets keep the prior error path.
  • Not tested locally: full cross-lane CI integration suite (run by CI). Local proof is the direct unit test of dispatchCronDelivery + tsgo:core.

Closes #56078

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

clawsweeper Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 7, 2026, 8:34 AM ET / 12:34 UTC.

Summary
The branch changes cron delivery dispatch, preview, gateway command-cron handling, and tests so unresolved implicit announce-to-last/no-channel delivery records ok/not-delivered while explicit or channel-resolved delivery failures still fail closed.

PR surface: Source +100, Tests +252. Total +352 across 8 files.

Reproducibility: yes. source-level: current main defaults detached cron output to announce/last, can resolve that no-channel target as ok:false, and then hard-fails unresolved non-best-effort delivery. I did not run the WSL2 Control UI scenario in this read-only review.

Review metrics: 1 noteworthy metric.

  • Delivery Semantics Changed: 1 implicit no-channel route class downgraded. The PR changes that route from run error to ok/not-delivered, which affects upgrade behavior, retries, and operator alerts.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #56078
Summary: This PR is the candidate fix for the open no-channel cron output regression; related cron delivery items cover broader defaults/status policy or adjacent delivery safety paths.

Members:

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

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦐 gold shrimp
Patch quality: 🐚 platinum hermit
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:

  • [P1] Add redacted installed no-channel agentTurn cron proof showing ok status, preserved output, delivered:false, and deliveryStatus:not-delivered.
  • Update the CLI cron docs to describe the skipped ok/not-delivered preview state.
  • Get maintainer acceptance or proof override for the compatibility-sensitive status change.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR comments include dispatcher-level output and loopback gateway command-cron proof, but not a redacted installed no-channel agentTurn cron transcript proving the linked user path preserves output and persists not-delivered metadata; redact private details before posting proof. 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] The PR intentionally changes implicit no-channel cron delivery from run error/backoff/failure-alert behavior to ok/not-delivered, so maintainers should explicitly accept that status semantic before merge.
  • [P1] The public proof now includes dispatcher and loopback command-cron output, but still not a redacted installed no-channel agentTurn cron transcript proving the linked user path preserves output and persists not-delivered metadata.
  • [P1] The CLI docs still describe channel:last previews as resolved or fail-closed even though this PR adds a skipped ok/not-delivered preview state.

Maintainer options:

  1. Gate On Real Cron Proof And Docs (recommended)
    Require a redacted installed no-channel agentTurn cron run proving ok status, preserved output, persisted not-delivered metadata, and update the CLI cron preview docs before merge.
  2. Accept The Semantic Shift Now
    Maintainers can intentionally accept ok/not-delivered based on focused source, gateway command-cron proof, and CI while owning the remaining user-path proof gap.
  3. Pause For Broader Cron Delivery Policy
    Pause or close this branch if the desired end state is a first-class warning status or default-delivery change instead of this targeted downgrade.

Next step before merge

  • [P1] Real behavior proof and maintainer acceptance of the cron status semantic are human merge gates, so this should not be routed to an automated repair lane even though the docs finding is narrow.

Maintainer decision needed

  • Question: Should implicit no-channel announce-to-last cron delivery become ok/not-delivered once proof and docs are supplied, or should this wait for a broader cron warning-status/default-delivery direction?
  • Rationale: The implementation is narrow and internally consistent, but it changes cron status semantics for existing no-channel runs and overlaps broader cron delivery policy work.
  • Likely owner: steipete — steipete has the strongest recent history in the cron delivery resolver and dispatch paths that define this semantic boundary.
  • Options:
    • Require Proof And Docs, Then Accept Narrow Fix (recommended): Ask for redacted installed no-channel agentTurn cron proof and the CLI docs update, then allow this bounded ok/not-delivered mitigation to proceed.
    • Override Proof Based On Focused Evidence: A maintainer may accept the dispatcher, gateway command-cron, and CI evidence as enough while owning the remaining end-to-end user-path proof risk.
    • Pause For Broader Status Model: Hold or close this branch if maintainers want the warning-status/default-delivery work to own the behavior instead.

Security
Cleared: The diff changes cron delivery control flow, preview formatting, gateway handling, and tests; it does not add dependency, workflow, secret, permission, package-resolution, or code-execution surface.

Review findings

  • [P3] Document the skipped last-channel preview state — src/cron/delivery-preview.ts:35-36
Review details

Best possible solution:

Land the narrow no-channel cron delivery fix only after docs alignment plus redacted installed no-channel agentTurn cron proof or maintainer proof override, while leaving broader default-delivery and warning-status policy to their canonical issues.

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

Yes, source-level: current main defaults detached cron output to announce/last, can resolve that no-channel target as ok:false, and then hard-fails unresolved non-best-effort delivery. I did not run the WSL2 Control UI scenario in this read-only review.

Is this the best way to solve the issue?

Yes as a bounded mitigation: the PR limits silent ok to the no-channel implicit route and preserves fail-closed behavior for explicit or channel-resolved failures. It still needs proof, docs alignment, and maintainer semantic acceptance before merge.

Full review comments:

  • [P3] Document the skipped last-channel preview state — src/cron/delivery-preview.ts:35-36
    This PR makes unresolved implicit last delivery display as skipped ok/not-delivered, but docs/cli/cron.md still says channel: "last" previews either resolve or fail closed. Please update the CLI cron docs so cron list/show output matches the new user-visible preview state.
    Confidence: 0.84

Overall correctness: patch is correct
Overall confidence: 0.84

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.

Label justifications:

  • P1: The PR targets a user-facing cron regression where useful cron work can be reported as a delivery-target failure in no-channel setups.
  • merge-risk: 🚨 compatibility: The patch changes existing cron run status and error semantics for implicit no-channel delivery from error to ok/not-delivered.
  • merge-risk: 🚨 message-delivery: The patch changes when cron delivery failures remain visible failed delivery versus silent not-delivery, affecting retries and operator expectations.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • 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 comments include dispatcher-level output and loopback gateway command-cron proof, but not a redacted installed no-channel agentTurn cron transcript proving the linked user path preserves output and persists not-delivered metadata; redact private details before posting proof. 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 +100, Tests +252. Total +352 across 8 files.

View PR surface stats
Area Files Added Removed Net
Source 4 106 6 +100
Tests 4 252 0 +252
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 8 358 6 +352

What I checked:

Likely related people:

  • steipete: Git blame and history show Peter Steinberger carrying the current cron delivery-plan, delivery-target, and dispatch hard-fail path now affected by the PR. (role: recent area contributor; confidence: high; commits: 395fbb8eb631, bf373eeb439e, 7a40d99b1d4d; files: src/cron/delivery-plan.ts, src/cron/isolated-agent/delivery-target.ts, src/cron/isolated-agent/delivery-dispatch.ts)
  • tyler6204: History ties Tyler Yust to isolated cron delivery modes and the announce default family involved in the no-channel route. (role: delivery default introducer; confidence: medium; commits: 511c656cbcb6; files: src/cli/cron-cli/register.cron-add.ts, ui/src/ui/app-defaults.ts)
  • shakkernerd: History ties Shakker to the split of cron delivery planning from sending, which is the boundary this PR reuses for explicit-target detection. (role: delivery-plan refactor author; confidence: medium; commits: de952c036abc; files: src/cron/delivery-plan.ts, src/cron/delivery.ts)
  • nxmxbbd: nxmxbbd authored the merged keyless implicit cron delivery fix in the same resolver/dispatch family, where channel-populated failures intentionally remain fail-closed. (role: recent adjacent contributor; confidence: high; commits: d4833e27c775; files: src/cron/isolated-agent/delivery-target.ts, src/cron/isolated-agent/delivery-dispatch.ts)
  • MoerAI: MoerAI has prior merged cron delivery-dispatch work and authored this candidate branch, so they are relevant for follow-up even though proposal authorship alone was not used as ownership. (role: adjacent contributor; confidence: medium; commits: 4140100807e9, 4fda0b52b7d9; files: src/cron/isolated-agent/delivery-dispatch.ts, src/cron/isolated-agent/run.ts, src/gateway/server-cron.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.
Review history (3 earlier review cycles)
  • reviewed 2026-07-02T11:45:01.881Z sha 69e0f54 :: needs real behavior proof before merge. :: [P3] Document the skipped last-channel preview state
  • reviewed 2026-07-06T06:46:06.331Z sha 69e0f54 :: needs real behavior proof before merge. :: [P3] Document the skipped last-channel preview state
  • reviewed 2026-07-07T12:00:56.105Z sha 4fda0b5 :: needs real behavior proof before merge. :: [P3] Document the skipped last-channel preview state

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4b3473fcd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// sendable target (local/no-channel setups, #56078); hard-erroring would
// mark a successful agent turn as failed and drop its output. Downgrade
// those to the silent-ok path, same as best-effort delivery already does.
if (params.deliveryTargetExplicit && !params.deliveryBestEffort) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep implicit target-resolution failures visible

When an omitted/default isolated cron delivery (channel: "last") resolves a channel but then fails to resolve the remembered or configured target, resolveDeliveryTarget returns ok:false with channel populated for the docked/target-resolution errors (src/cron/isolated-agent/delivery-target.ts:308-335). Because such jobs have no concrete fields in the delivery plan, deliveryTargetExplicit is false, so this new guard now downgrades those real delivery failures to status:"ok" with no retry/alert instead of failing the run. Limit the silent-ok downgrade to the no-channel/no-previous-channel case (or otherwise check the failure shape) so stale/invalid implicit routes do not get silently dropped.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 3745614e. Good catch — you're right that resolveDeliveryTarget returns ok:false with channel populated for the docked/target-resolution failures (src/cron/isolated-agent/delivery-target.ts:308-335, 348-354), which are real delivery failures that must stay visible. The original guard keyed only on deliveryTargetExplicit, so an implicit job whose channel resolved but whose remembered/configured route was stale/invalid would have been wrongly downgraded to status:"ok".

The downgrade is now limited to the genuine no-channel case:

const targetResolvedButFailed = params.resolvedDelivery.channel != null;
if (!params.deliveryBestEffort && (params.deliveryTargetExplicit || targetResolvedButFailed)) {
  return { result: failDeliveryTarget(...), ... };
}

So only channel == null (announce-to-last with no previous channel, delivery-target.ts:249-259) downgrades to silent-ok; any ok:false that carries a resolved channel still fail-closes with errorKind:"delivery-target" (retry/alert preserved). Added a regression case still fails an implicit delivery whose resolved channel has a stale/invalid target (#56078 P1).

pnpm test src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts — 71 passed; pnpm tsgo:core — clean.

@MoerAI MoerAI Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 69e0f54 (fix(cron): preserve silent no-channel delivery state (#56078)).

The missing piece was in finalizeCronRun: when dispatch returned a silent-ok result for implicit delivery.channel=last with no previous channel, the dispatch state had delivered: false but the result object itself did not. The runner now preserves deliveryResult.delivered when merging delivery metadata, so the successful cron run persists as not delivered instead of falling back to unknown.

Verification:

  • node scripts/run-vitest.mjs src/cron/isolated-agent/run.message-tool-policy.test.ts — passed
  • pnpm exec oxfmt --check --threads=1 src/cron/isolated-agent/run.ts src/cron/isolated-agent/run.message-tool-policy.test.ts — passed
  • pnpm tsgo — passed (engine warning only: local Node v22.14.0 vs repo wants >=22.19.0)
  • LSP diagnostics clean for both changed files

@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. labels Jun 8, 2026
@MoerAI
MoerAI force-pushed the fix/cron-no-channel-output branch from b4b3473 to 3745614 Compare June 9, 2026 01:26
@clawsweeper clawsweeper Bot added 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 9, 2026
@MoerAI
MoerAI force-pushed the fix/cron-no-channel-output branch from 3745614 to 68b41e5 Compare June 22, 2026 02:38
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 22, 2026
@MoerAI

MoerAI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Refreshed onto current main and addressed the codex review.

Preview alignment (P2 — resolveCronDeliveryPreview vs runtime)

Applied in 68b41e59b. src/cron/delivery-preview.ts now mirrors the exact dispatch predicate instead of always reporting an unresolved last target as will fail-closed. The preview shows will fail-closed only when !bestEffort && (hasExplicitCronDeliveryTarget(plan) || resolved.channel != null), and the implicit no-channel case is shown as will skip delivery (ok, not delivered) — matching the runtime silent-ok downgrade. Added delivery-preview.test.ts coverage for both sides: implicit-no-channel (silent ok) and channel-resolved-but-stale (fail-closed).

Branch refresh

Rebased the fix onto current main, preserving the merged deleteAfterRun unresolved-target cleanup (cleanupDirectCronSessionIfNeeded() ahead of the hard-fail gate), so GitHub can compute a clean merge result again.

Local validation

  • node scripts/run-vitest.mjs src/cron/delivery-preview.test.ts src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts — green (--passWithNoTests=false, so the new cases are exercised).
  • node scripts/run-tsgo.mjs -p tsconfig.core.json and -p test/tsconfig/tsconfig.core.test.json — no errors on the touched cron files. Both lanes surface one pre-existing, unrelated src/auto-reply/reply/agent-runner-utils.ts TS2305 (resolveEnforceFinalTagWithResolver) that already fails on current main independent of this PR.

Still open

The real-behavior proof you flagged — a redacted local no-channel cron run showing ok status + preserved output + not-delivered metadata — is the remaining contributor item. I'd rather flag that honestly than attach a synthetic artifact. The preview/runtime mismatch and the branch conflict are both resolved.

@MoerAI
MoerAI force-pushed the fix/cron-no-channel-output branch 3 times, most recently from dfef8a8 to 557c7f4 Compare June 23, 2026 11:13
@clawsweeper clawsweeper Bot added status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. status: 🛠️ actively grinding The PR author has acted after the latest ClawSweeper review and work remains. labels Jun 26, 2026
@openclaw-barnacle openclaw-barnacle Bot added size: M and removed size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 2, 2026
@MoerAI

MoerAI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the #56078 proof gap and reran the focused behavior checks on the current head (69e0f54fb57717fb0cbbfd3b7d7a6783cb51acad).

Proof run 1: focused regression tests for the two important branches:

pnpm test src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts -t "downgrades an unresolved implicit last-channel delivery to a silent ok|still fails an implicit delivery whose resolved channel has a stale/invalid target" --reporter verbose

Result: PASS, 1 file, 2 tests passed, 83 skipped.

Proof run 2: Node 22.19 local runtime proof against dispatchCronDelivery for the unresolved implicit delivery.channel=last / no previous channel shape. This uses the real dispatcher path, not a mocked dispatcher. The resolved target intentionally has no channel, which is the #56078 case that should downgrade to silent-ok.

{
  "node": "v22.19.0",
  "plan": {
    "mode": "announce",
    "channel": "last",
    "requested": true,
    "explicit": false
  },
  "resolved": {
    "ok": false,
    "mode": "implicit",
    "error": "Channel is required when delivery.channel=last has no previous channel."
  },
  "dispatch": {
    "resultStatus": "ok",
    "delivered": false,
    "deliveryAttempted": false,
    "resultDeliveryAttempted": false
  }
}

Control proof: explicit/stale delivery target still hard-fails, so the P1 guard remains intact.

{
  "node": "v22.19.0",
  "plan": {
    "mode": "announce",
    "channel": "telegram",
    "to": "chat:missing",
    "requested": true,
    "explicit": true
  },
  "resolved": {
    "ok": false,
    "channel": "telegram",
    "mode": "explicit",
    "error": "Delivering to Telegram requires target <chatId>"
  },
  "dispatch": {
    "resultStatus": "error",
    "resultError": "Delivering to Telegram requires target <chatId>",
    "delivered": false,
    "deliveryAttempted": false,
    "resultDeliveryAttempted": false
  }
}

A full CLI/gateway proof was attempted first, but the local gateway path required pairing/token auth before cron add/cron run would execute. The dispatcher-level runtime proof above exercises the changed delivery branch directly while keeping the gateway auth issue out of scope.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jul 6, 2026
@MoerAI
MoerAI force-pushed the fix/cron-no-channel-output branch from 69e0f54 to 4ed8b86 Compare July 7, 2026 11:38
@openclaw-barnacle openclaw-barnacle Bot added the gateway Gateway runtime label Jul 7, 2026
MoerAI added 5 commits July 7, 2026 20:52
…ailing delivery (openclaw#56078)

Backend/Control-UI isolated agentTurn cron jobs with no explicit delivery config default to implicit announce-to-last (delivery-plan.ts resolveCronDeliveryPlan). On a local/no-channel setup resolveDeliveryTarget returns ok:false ('Channel is required when delivery.channel=last has no previous channel'), and dispatchCronDelivery hard-failed the whole run (status=error), so a successful agent turn that e.g. wrote a report file was reported as failed (issue openclaw#56078). Now the unresolved-target hard-fail is gated on deliveryTargetExplicit=hasExplicitCronDeliveryTarget(deliveryPlan): implicit announce-to-last with no channel downgrades to the existing silent-ok path (delivered=false), while explicit targets still fail-closed (no message loss for intended deliveries). Regression test covers both sides. 70/70 delivery-dispatch.double-announce.test.ts pass; tsgo core clean.
…penclaw#56078 codex P1)

Codex P1 on delivery-dispatch.ts:1232: the deliveryTargetExplicit gate also downgraded implicit jobs whose channel resolved but whose remembered/configured target was stale/invalid (delivery-target.ts:308-335 returns ok:false WITH channel populated) — real failures that must stay visible with retry/alert. Now only the no-channel case (resolvedDelivery.channel == null) downgrades to silent-ok; channel-resolved target-resolution failures still fail-closed. Added regression test for the channel-populated implicit case. 71/71 delivery-dispatch.double-announce.test.ts pass; tsgo core clean.
…penclaw#56078 codex P2)

Codex P2 on delivery-dispatch.ts:1234: the runtime now returns ok/not-delivered for an implicit announce-to-last cron run with no resolvable channel (openclaw#56078), but resolveCronDeliveryPreview still formatted that same unresolved last target as 'will fail-closed', so CLI/UI delivery previews contradicted runtime. The preview now mirrors the exact runtime predicate: an unresolved last target is shown fail-closed only when !bestEffort AND (hasExplicitCronDeliveryTarget(plan) OR a channel resolved but its route is stale, resolved.channel != null); the implicit no-channel case is shown 'will skip delivery (ok, not delivered)'. Added focused preview regression coverage for both the implicit-no-channel (silent ok) and channel-resolved-but-stale (fail-closed) sides. delivery-preview.test.ts + delivery-dispatch.double-announce.test.ts pass; tsgo core + core:test clean on touched files.
Preserve the dispatch-level delivered=false metadata when unresolved implicit announce-to-last delivery returns a silent ok result. This keeps successful no-channel cron runs visible as not delivered instead of falling back to an unknown delivery state.\n\nVerification:\n- node scripts/run-vitest.mjs src/cron/isolated-agent/run.message-tool-policy.test.ts\n- pnpm exec oxfmt --check --threads=1 src/cron/isolated-agent/run.ts src/cron/isolated-agent/run.message-tool-policy.test.ts\n- pnpm tsgo
Command cron jobs inherit the announce-to-last default used by detached cron output. When the command exits successfully but the implicit last-channel target cannot resolve because there is no channel history or configured channel, record the run as successful with not-delivered metadata instead of converting the command result into a run failure.

Constraint: Explicit or stale configured delivery targets must continue to fail the run.
Rejected: Treat every command delivery failure as best-effort | would hide configured target breakages.
Confidence: high
Scope-risk: narrow
Tested: focused gateway regression for implicit command no-channel runs
Tested: focused cron delivery preview and isolated dispatch regressions
Tested: oxfmt check on eight PR files
Tested: tsdown build --no-clean
Tested: live loopback token-auth gateway with OPENCLAW_SKIP_CHANNELS=1; implicit command cron stayed ok/not-delivered and explicit telegram target stayed error
Not-tested: pnpm check:changed broad Testbox gate
@MoerAI
MoerAI force-pushed the fix/cron-no-channel-output branch from 4ed8b86 to 4fda0b5 Compare July 7, 2026 11:53
@MoerAI

MoerAI commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased and pushed a current-main proof update at 4fda0b52b7d9a39db81f2adfadb46bb98d97e3c3.

What changed in this update:

  • Closed the command-cron gap in src/gateway/server-cron.ts: implicit announce/last delivery failures caused only by no configured/previous channel now keep a successful command run as ok + not-delivered instead of turning the run into an error.
  • Kept explicit/configured-target failures fail-closed by requiring successful command status, implicit delivery source, announce mode, and no explicit delivery target before downgrading.
  • Added regression coverage in src/gateway/server.cron.test.ts for an isolated command cron created without a delivery field.

Live behavior proof on rebuilt gateway bundle:

  • Built with npx -y node@24 scripts/tsdown-build.mjs --no-clean.
  • Started a loopback-only token-auth Gateway with OPENCLAW_SKIP_CHANNELS=1; GET /healthz returned HTTP 200 and {"ok":true,"status":"live"}.
  • Direct WebSocket protocol 4 RPC created an isolated command cron with no delivery field and forced the run.
  • Run result: status:"ok", summary:"OPENCLAW_91399_DIRECT_IMPLICIT", no top-level error, delivered:false, deliveryStatus:"not-delivered", delivery.intended:{channel:"last",source:"last"}, delivery.resolved:{channel:"last",source:"last",ok:false,error:"Channel is required ... no configured channels detected ..."}.
  • Explicit guard proof: direct WebSocket RPC with delivery:{mode:"announce",channel:"telegram",to:"123"} still produced status:"error", summary:"OPENCLAW_91399_EXPLICIT_GUARD_RERUN", delivered:false, and delivery.intended/resolved.source:"explicit" with resolved.ok:false.

Verification on final head:

  • pnpm changed:lanes --json --base upstream/main --head HEAD selected only core and coreTests for the eight cron/gateway files.
  • OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_LOCAL_CHECK=1 OPENCLAW_LOCAL_CHECK_MODE=throttled CI=1 npx -y node@24 <pnpm.cjs> check:changed --base upstream/main --head HEAD --timed passed on final head: core typecheck, core-test typecheck, targeted core oxlint, import-cycle guard, runtime sidecar loader guard, pairing guards, dependency guards, and the other changed-gate guards were all ok.
  • Focused tests passed before the final clean rebase and the rebase had no conflicts and no diff-surface change: src/gateway/server.cron.test.ts -t "keeps implicit no-channel command cron runs successful", src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts -t "downgrades an unresolved implicit last-channel delivery to a silent ok|still fails an implicit delivery whose resolved channel has a stale/invalid target", and src/cron/delivery-preview.test.ts -t "silent ok|fail-closed".
  • oxfmt --check passed on all eight PR files.

Note: Blacksmith/Testbox CLI is unavailable on this native Windows host (blacksmith installer rejects mingw64_nt), so I used the repo child changed-gate path locally for the broad changed gate and left GitHub CI to continue on the pushed SHA.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 7, 2026
@MoerAI

MoerAI commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Closing this one on my side. The fix is real and still reproduces on current main, but the remaining merge gate is a maintainer product decision I can't resolve as an external contributor: it changes no-channel implicit cron delivery status semantics (errorok/not-delivered, flagged merge-risk: compatibility + merge-risk: message-delivery), which the review calls out as needing explicit maintainer acceptance. It also overlaps the broader cron-delivery-policy direction in #90626 and the execution-success-vs-delivery-failure status-model discussion in #49190 / #56078.

Rather than hold this open while the direction is decided in those canonical threads, I'm closing it and leaving the no-channel cron status call to them. If a maintainer wants this narrow ok/not-delivered mitigation to proceed, I'm happy to reopen and add the installed no-channel agentTurn cron transcript proof. Verified against current main (60f0749).

@MoerAI MoerAI closed this Jul 14, 2026
@MoerAI
MoerAI deleted the fix/cron-no-channel-output branch July 15, 2026 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime 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: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: M status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Cron Job Output Not Saved in OpenClaw 2026.3.24

1 participant