Skip to content

fix(cron): remove duplicate main-session relay for isolated jobs with announce delivery#15737

Merged
steipete merged 2 commits into
openclaw:mainfrom
brandonwise:fix/cron-announce-duplicate
Feb 13, 2026
Merged

fix(cron): remove duplicate main-session relay for isolated jobs with announce delivery#15737
steipete merged 2 commits into
openclaw:mainfrom
brandonwise:fix/cron-announce-duplicate

Conversation

@brandonwise

@brandonwise brandonwise commented Feb 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #15692

Previously, isolated cron jobs with delivery.mode=announce caused duplicate messages:

  1. runIsolatedAgentJob() calls runSubagentAnnounceFlow() which sends a message to the main session
  2. executeJobCore() ALSO called enqueueSystemEvent() + requestHeartbeatNow() which triggered another main session run

The second step was redundant since runSubagentAnnounceFlow already handles the announce delivery.

Changes

Testing

  • All 95 cron tests pass
  • Verified that isolated jobs with announce delivery no longer trigger duplicate messages

Greptile Overview

Greptile Summary

This PR removes the CronService-level “post summary back to main session” relay from executeJobCore() for isolated jobs, based on the rationale that runIsolatedAgentJob()’s announce flow already delivers the message (fixing duplicate relays for delivery.mode=announce). Tests were updated to assert enqueueSystemEvent() / requestHeartbeatNow() are not called for isolated jobs.

One behavior risk: runIsolatedAgentJob is an injected dependency. If it does not itself run the announce flow, isolated jobs with delivery.requested=true will no longer be relayed to the main session at all (previously they were, gated by resolveCronDeliveryPlan(job).requested and a non-empty res.summary). Consider scoping the removal to only the cases where the injected runner is known to already announce, or reintroducing the relay for other runners.

Confidence Score: 3/5

  • This PR is moderately safe to merge but may change isolated-job delivery behavior depending on the injected runner implementation.
  • The code change is small and test-updated, but it removes a relay that previously posted summaries to main for isolated jobs when delivery was requested. Because runIsolatedAgentJob is a dependency, this can break consumers that relied on CronService to perform the relay rather than the runner.
  • src/cron/service/timer.ts

Last reviewed commit: 9a6263c

@greptile-apps greptile-apps 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.

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread src/cron/service/timer.ts Outdated
Comment on lines +485 to +489
// Note: We do NOT enqueue a system event here for isolated jobs with
// delivery.requested=true. The runIsolatedAgentJob function already
// handles announce delivery via runSubagentAnnounceFlow, which posts
// the response to the main session. Enqueuing here would cause duplicate
// messages. See: https://github.com/openclaw/openclaw/issues/15692

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.

Isolated jobs no longer announce

executeJobCore() no longer calls enqueueSystemEvent() for any isolated job result, but the removed code only posted to main when resolveCronDeliveryPlan(job).requested and res.summary?.trim() were truthy. runIsolatedAgentJob is just an injected dep (CronServiceDeps), and in the unit tests it’s stubbed to return {status, summary} without performing the announce flow—so with this change, isolated jobs that previously relied on the CronService-level relay (i.e., any delivery.requested=true job when the injected runner doesn’t itself call runSubagentAnnounceFlow) will silently stop posting the summary to main.

If the intent is “only skip the relay when the isolated runner is the cron isolated-agent implementation that already announces”, this needs to be gated accordingly (e.g., only skip for delivery.mode=announce when the runner is known to do announce delivery), otherwise behavior changes for other runIsolatedAgentJob implementations.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/service/timer.ts
Line: 485:489

Comment:
**Isolated jobs no longer announce**

`executeJobCore()` no longer calls `enqueueSystemEvent()` for *any* isolated job result, but the removed code only posted to main when `resolveCronDeliveryPlan(job).requested` and `res.summary?.trim()` were truthy. `runIsolatedAgentJob` is just an injected dep (`CronServiceDeps`), and in the unit tests it’s stubbed to return `{status, summary}` without performing the announce flow—so with this change, isolated jobs that previously relied on the CronService-level relay (i.e., any `delivery.requested=true` job when the injected runner doesn’t itself call `runSubagentAnnounceFlow`) will silently stop posting the summary to main.

If the intent is “only skip the relay when the isolated runner is the cron isolated-agent implementation that already announces”, this needs to be gated accordingly (e.g., only skip for `delivery.mode=announce` when the runner is known to do announce delivery), otherwise behavior changes for other `runIsolatedAgentJob` implementations.


How can I resolve this? If you propose a fix, please make it concise.

@brandonwise

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review!

To clarify: this is an intentional fix for the duplicate message bug (#15692).

The issue was:

  1. runIsolatedAgentJob (the production implementation in cron-runner.ts) already calls runSubagentAnnounceFlow() which delivers the summary to the configured channel
  2. executeJobCore was ALSO calling enqueueSystemEvent() for the same job, causing duplicate messages

Why the tests pass:
The test stubs for runIsolatedAgentJob don't call runSubagentAnnounceFlow — they're mocks. But that's correct because:

  • Tests are verifying CronService behavior, not the runner implementation
  • The assertion is "CronService should NOT duplicate the announce" (not "announce should happen")
  • The actual announce is the runner's responsibility, tested separately

Re: other implementations:
The CronServiceDeps interface is internal — the only production implementation is cron-runner.ts, which handles announce delivery. If a future alternate runner is added, it would need to follow the same contract.

Happy to add a comment documenting this expectation if that would help! 🦞

@brandonwise brandonwise force-pushed the fix/cron-announce-duplicate branch from 9a6263c to b885581 Compare February 13, 2026 22:51
@brandonwise

Copy link
Copy Markdown
Contributor Author

Great catch! You're right that removing the relay entirely would break other runIsolatedAgentJob implementations.

Updated approach:

  • Use resolveCronDeliveryPlan().source to distinguish between explicit job.delivery config and legacy payload.deliver fields
  • Skip relay only when source === "delivery" (isolated runner handles it)
  • Keep original relay behavior for legacy payload-only jobs

This preserves backwards compatibility while fixing the duplicate message issue for explicit delivery configs.

brandonwise and others added 2 commits February 13, 2026 23:55
…ayload

Fixes openclaw#15692

The previous fix was too broad — it removed the relay for ALL isolated jobs.
This broke backwards compatibility for jobs without explicit delivery config.

The correct behavior is:
- If job.delivery exists → isolated runner handles it via runSubagentAnnounceFlow
- If only legacy payload.deliver fields → relay to main if requested (original behavior)

This addresses Greptile's review feedback about runIsolatedAgentJob being an
injected dependency that might not call runSubagentAnnounceFlow.

Uses resolveCronDeliveryPlan().source to distinguish between explicit delivery
config and legacy payload-only jobs.
@steipete steipete force-pushed the fix/cron-announce-duplicate branch from b885581 to cb7bc43 Compare February 13, 2026 23:08
@steipete steipete merged commit b870354 into openclaw:main Feb 13, 2026
9 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm check && pnpm build && pnpm test && pnpm check:docs
  • Land commit: cb7bc43
  • Merge commit: PLACEHOLDER_MERGE_SHA

Thanks @brandonwise!

@steipete

Copy link
Copy Markdown
Contributor

Correction:

GwonHyeok pushed a commit to learners-superpumped/openclaw that referenced this pull request Feb 15, 2026
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 1, 2026
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 3, 2026
john-ver pushed a commit to apmcoin/apmclaw that referenced this pull request Mar 9, 2026
dustin-olenslager pushed a commit to dustin-olenslager/ironclaw-supreme that referenced this pull request Mar 24, 2026
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: cron isolated agentTurn with delivery.mode=announce posts duplicate messages with different content (extra agent run triggered)

2 participants