fix(cron): pass heartbeat target=last for main-session cron jobs (#28508)#28583
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6680124eec
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/gateway/server-cron.ts
Outdated
| const baseHeartbeat = runtimeConfig.agents?.defaults?.heartbeat; | ||
| const heartbeatOverride = opts?.heartbeat | ||
| ? { ...baseHeartbeat, ...opts.heartbeat } |
There was a problem hiding this comment.
Merge cron heartbeat override with resolved agent heartbeat
When opts.heartbeat is present, this path builds the override from runtimeConfig.agents?.defaults?.heartbeat only, so any per-agent heartbeat overrides are dropped for cron wakeMode: "now" runs. Because runHeartbeatOnce uses opts.heartbeat directly (and does not call its internal resolveHeartbeatConfig merge in that case), non-default agents can lose settings like every, prompt, model, accountId, or custom targeting during cron-triggered heartbeats. This regresses multi-agent configurations where cron jobs are scoped to an agent with heartbeat overrides.
Useful? React with 👍 / 👎.
Greptile SummaryRestores delivery for cron-triggered heartbeats after recent default change from Key changes:
The implementation correctly preserves the new Confidence Score: 5/5
Last reviewed commit: d45aa29 |
6680124 to
d45aa29
Compare
|
@greptile-apps please re-review — fixed heartbeat config merge to resolve agent-specific config (agents.list[].heartbeat) before falling back to defaults, addressing the edge case noted in previous review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d45aa29b5c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const agentEntry = | ||
| Array.isArray(runtimeConfig.agents?.list) && | ||
| runtimeConfig.agents.list.find( | ||
| (entry) => | ||
| entry && typeof entry.id === "string" && normalizeAgentId(entry.id) === agentId, |
There was a problem hiding this comment.
Resolve default agent before heartbeat override merge
For wakeMode: "now" main jobs that omit agentId (the normal default-agent path), resolveCronWakeTarget can return agentId as undefined, so this lookup never matches an agents.list entry. When opts.heartbeat is set, heartbeatOverride is then built from agents.defaults.heartbeat only and passed into runHeartbeatOnce, which bypasses the runner’s own resolveHeartbeatConfig merge and drops default-agent agents.list[].heartbeat overrides such as model, prompt, accountId, or every. Fresh evidence is that executeJobCore still forwards optional job.agentId directly for these cron runs, so this undefined-agent case is reachable.
Useful? React with 👍 / 👎.
When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508
d45aa29 to
bbf4d10
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74d8034c4c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const heartbeatOverride = opts?.heartbeat | ||
| ? { ...baseHeartbeat, ...opts.heartbeat } |
There was a problem hiding this comment.
Resolve default agent before merging heartbeat override
For sessionTarget: "main" + wakeMode: "now" jobs that omit agentId (the normal default-agent path), resolveCronWakeTarget leaves agentId undefined, so baseHeartbeat includes only agents.defaults.heartbeat and misses agents.list[defaultAgent].heartbeat; then this heartbeatOverride is passed to runHeartbeatOnce, which bypasses its internal resolveHeartbeatConfig merge and drops default-agent overrides like model, prompt, and accountId. Fresh evidence is that this commit now always sends heartbeat: { target: "last" } from executeJobCore, so the override path is exercised on every such cron run.
Useful? React with 👍 / 👎.
* main: (31 commits) fix(browser): resolve correct targetId in navigate response after renderer swap (openclaw#25326) fix: sed escaping and UID mismatch in Podman Quadlet setup (openclaw#26414) fix(cron): pass heartbeat target=last for main-session cron jobs (openclaw#28508) (openclaw#28583) fix(cron): disable messaging tool when delivery.mode is none (openclaw#21808) (openclaw#21896) fix: clear delivery routing state when creating isolated cron sessions (openclaw#27778) fix(cron): avoid marking queued announce paths as delivered (openclaw#29716) fix(cron): enable completion direct send for text-only announce delivery (openclaw#29151) fix(cron): force main-target system events onto main session (openclaw#28898) fix(cron): condition requireExplicitMessageTarget on resolved delivery (openclaw#28017) feat(cron): add --account flag for multi-account delivery routing (openclaw#26284) fix: schedule nextWakeAtMs for isolated sessionTarget cron jobs (openclaw#19541) fix: sandbox browser docker no-sandbox rollout (openclaw#29879) (thanks @Lukavyi) GitHub: add regression bug issue template and routing (openclaw#29864) thanks @Takhoffman feat(feishu): add chat info/member tool (openclaw#14674) feat(feishu): add markdown tables, positional insert, color_text, and table ops (openclaw#29411) feat(feishu): add parent/root inbound context for quote support (openclaw#18529) fix: land android onboarding and voice reliability updates (openclaw#29796) fix(android-voice): rotate playback token per assistant reply fix(android-voice): retry talk config after transient failures fix(android-voice): cancel in-flight speech when speaker muted ...
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…nclaw#28508) (openclaw#28583) * fix(cron): pass heartbeat target=last for main-session cron jobs When a cron job with sessionTarget=main and wakeMode=now fires, it triggers a heartbeat via runHeartbeatOnce. Since e2362d3 changed the default heartbeat target from "last" to "none", these cron-triggered heartbeats silently discard their responses instead of delivering them to the last active channel (e.g. Telegram). Fix: pass heartbeat: { target: "last" } from the cron timer to runHeartbeatOnce for main-session jobs, and wire the override through the gateway cron service builder. This restores delivery for sessionTarget=main cron jobs without reverting the intentional default change for regular heartbeats. Regression introduced in: e2362d3 (2026-02-25) Fixes openclaw#28508 * Cron: align server-cron wake routing expectations for main-target jobs --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Problem
Scheduled cron jobs with
sessionTarget: "main"andwakeMode: "now"stopped delivering responses to messaging channels (Telegram, WhatsApp, etc.) after v2026.2.26.Who is affected: All users with
sessionTarget: "main"cron jobs that expect delivery to their messaging channel.Root Cause
Commit
e2362d35("fix(heartbeat): default target none and internalize relay prompts", Feb 25) intentionally changed the default heartbeat target from"last"to"none"to prevent unsolicited heartbeat messages. However, when cron jobs withsessionTarget: "main"fire withwakeMode: "now", they triggerrunHeartbeatOnce()to execute the agent turn. Without an explicit heartbeat target override, the new"none"default causes the heartbeat runner to silently discard the response instead of delivering it to the last active channel.Specific files:
src/infra/outbound/targets.ts:210—resolveHeartbeatDeliveryTarget()defaults to"none"and returns early without any delivery targetsrc/cron/service/timer.ts:655—runHeartbeatOnce()called without heartbeat config overrideFix
Three surgical changes:
src/cron/service/timer.ts: Passheartbeat: { target: "last" }torunHeartbeatOnce()when executingsessionTarget: "main"cron jobs withwakeMode: "now". This ensures cron-triggered heartbeats deliver to the last active channel.src/cron/service/state.ts: ExtendCronServiceDeps.runHeartbeatOncetype to accept an optionalheartbeatconfig parameter.src/gateway/server-cron.ts: Wire the heartbeat config override through the gateway cron service builder, merging it with the existing agent heartbeat config.This preserves the intentional
"none"default for regular periodic heartbeats while restoring delivery for cron-triggered ones.Testing
New test file:
src/cron/service.main-job-passes-heartbeat-target-last.test.tsrunHeartbeatOncereceivesheartbeat.target = "last"forwakeMode: "now"main jobswakeMode: "next-heartbeat"jobs userequestHeartbeatNow(notrunHeartbeatOnce)@greptile-apps
Fixes #28508