fix(cron): preserve deferred heartbeat target override#69021
fix(cron): preserve deferred heartbeat target override#69021obviyus merged 8 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a bug where the Confidence Score: 5/5Safe to merge — the fix is minimal, well-scoped, and covered by targeted regression tests. No P0 or P1 issues found. The retry preservation logic in heartbeat-wake.ts is correct: both the requests-in-flight path (lines 176–181) and the error-catch path (lines 187–193) now forward heartbeat to queuePendingWakeReason. The wakeHandler forwarding in heartbeat-runner.ts and the two requestHeartbeatNow callsites in timer.ts are all consistent. Test coverage directly exercises every changed path. No files require special attention. Reviews (1): Last reviewed commit: "fix(cron): preserve deferred heartbeat t..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01d908a15a
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a3255a816
ℹ️ 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".
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟡 Heartbeat delivery target override can leak across coalesced wakes ("last" target persists)
Description
This can unintentionally apply Impact:
Vulnerable code: const merged =
(next.heartbeat ?? previous.heartbeat)
? { ...next, heartbeat: next.heartbeat ?? previous.heartbeat }
: next;The coalescing key does not include RecommendationEnsure heartbeat overrides do not unintentionally persist across coalesced wakes. Options:
function getWakeTargetKey(params: { agentId?: string; sessionKey?: string; heartbeatTarget?: string }) {
const agentId = normalizeWakeTarget(params.agentId);
const sessionKey = normalizeWakeTarget(params.sessionKey);
const hbTarget = normalizeOptionalString(params.heartbeatTarget) ?? "";
return `${agentId ?? ""}::${sessionKey ?? ""}::${hbTarget}`;
}
// keep next.heartbeat as-is; do not fall back to previous
pendingWakes.set(wakeTargetKey, next);
Also consider normalizing/validating 2. 🟡 Prototype pollution via merging untrusted heartbeat override object
DescriptionThe heartbeat wake path merges a caller-provided
Vulnerable code: const resolveRequestedHeartbeat = (heartbeat?: HeartbeatConfig) =>
requestedHeartbeat ? { ...heartbeat, ...requestedHeartbeat } : heartbeat;RecommendationHarden merges of externally-provided objects:
Example: function sanitizeHeartbeatOverride(input: unknown): { target?: string } | undefined {
if (!input || typeof input !== "object") return undefined;
const rec = input as Record<string, unknown>;
// block prototype-pollution keys explicitly
if ("__proto__" in rec || "constructor" in rec || "prototype" in rec) return undefined;
const out: { target?: string } = {};
if (typeof rec.target === "string") out.target = rec.target;
return out;
}
const safeOverride = sanitizeHeartbeatOverride(params?.heartbeat);
const resolveRequestedHeartbeat = (heartbeat?: HeartbeatConfig) =>
safeOverride ? { ...heartbeat, ...safeOverride } : heartbeat;Apply the same pattern anywhere heartbeat overrides are merged (e.g., the gateway cron adapter). 3. 🟡 Cron-triggered heartbeat forces delivery to session's "last" route (potential cross-user data exposure)
DescriptionMain-session cron jobs now always wake/execute heartbeats with an override This is risky because If the agent’s main session is shared by multiple users/chats (common for a single agent main session), a cron-triggered heartbeat can be delivered to an unintended recipient (the most recent user), leaking:
Vulnerable code (new behavior): heartbeatResult = await state.deps.runHeartbeatOnce({
reason,
agentId: job.agentId,
sessionKey: targetMainSessionKey,
heartbeat: { target: "last" },
});
...
state.deps.requestHeartbeatNow({
reason,
agentId: job.agentId,
sessionKey: targetMainSessionKey,
heartbeat: { target: "last" },
});Because this override is unconditional for main-session cron jobs, it can also bypass an agent configuration that intentionally sets RecommendationAvoid forcing Options:
const heartbeatOverride = job.heartbeatTargetOverride
? { target: job.heartbeatTargetOverride }
: undefined;
state.deps.requestHeartbeatNow({ reason, agentId: job.agentId, sessionKey: targetMainSessionKey, heartbeat: heartbeatOverride });
Analyzed PR: #69021 at commit Last updated on: 2026-04-19T17:22:07Z |
* test(cron): cover deferred heartbeat target preservation * fix(cron): preserve deferred heartbeat target override * test(cron): update timer expectation for deferred heartbeat target * fix(cron): preserve agent heartbeat config for targeted wakes * test(cron): use wake request type in scheduler helper * fix(cron): forward heartbeat overrides through gateway wake adapter * fix(cron): preserve coalesced wake heartbeat overrides * fix: preserve deferred cron heartbeat target (openclaw#69021)
* test(cron): cover deferred heartbeat target preservation * fix(cron): preserve deferred heartbeat target override * test(cron): update timer expectation for deferred heartbeat target * fix(cron): preserve agent heartbeat config for targeted wakes * test(cron): use wake request type in scheduler helper * fix(cron): forward heartbeat overrides through gateway wake adapter * fix(cron): preserve coalesced wake heartbeat overrides * fix: preserve deferred cron heartbeat target (openclaw#69021)
* test(cron): cover deferred heartbeat target preservation * fix(cron): preserve deferred heartbeat target override * test(cron): update timer expectation for deferred heartbeat target * fix(cron): preserve agent heartbeat config for targeted wakes * test(cron): use wake request type in scheduler helper * fix(cron): forward heartbeat overrides through gateway wake adapter * fix(cron): preserve coalesced wake heartbeat overrides * fix: preserve deferred cron heartbeat target (openclaw#69021)
* test(cron): cover deferred heartbeat target preservation * fix(cron): preserve deferred heartbeat target override * test(cron): update timer expectation for deferred heartbeat target * fix(cron): preserve agent heartbeat config for targeted wakes * test(cron): use wake request type in scheduler helper * fix(cron): forward heartbeat overrides through gateway wake adapter * fix(cron): preserve coalesced wake heartbeat overrides * fix: preserve deferred cron heartbeat target (openclaw#69021)
Keep the main-session cron heartbeat override when a wake is queued or retried instead of dropping back to the default heartbeat target.
Adds regressions for deferred main-session cron wakes and the wake-queue retry path.