Skip to content

fix(heartbeat): type wake scheduling intent#76086

Merged
steipete merged 2 commits intomainfrom
pr-75439-best
May 2, 2026
Merged

fix(heartbeat): type wake scheduling intent#76086
steipete merged 2 commits intomainfrom
pr-75439-best

Conversation

@steipete
Copy link
Copy Markdown
Contributor

@steipete steipete commented May 2, 2026

Summary

  • Replace string-reason heartbeat wake policy with typed source / intent wake requests.
  • Keep event-driven exec/notification/spawn/retry wakes behind the centralized cooldown while preserving documented wake-now paths.
  • Preserve the deprecated plugin runtime.system.requestHeartbeatNow() alias while adding the structured API.

Credits

Co-authored-by: Jordan Baker jbb@scryent.com

Test plan

  • pnpm test src/infra/heartbeat-reason.test.ts src/infra/heartbeat-cooldown.test.ts src/infra/heartbeat-wake.test.ts src/infra/heartbeat-runner.scheduler.test.ts src/cron/service.main-job-passes-heartbeat-target-last.test.ts src/cron/service/timer.test.ts src/gateway/server-cron.test.ts src/plugins/runtime/index.test.ts
  • git diff --check origin/main..HEAD

Replaces #75439.
Closes #64016.
Refs #17797, #75436.

@steipete steipete requested a review from a team as a code owner May 2, 2026 13:32
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime cli CLI command changes agents Agent runtime and tooling plugin: file-transfer size: XL maintainer Maintainer-authored PR labels May 2, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 2, 2026

Codex review: needs changes before merge.

Summary
The PR replaces string heartbeat wake reasons with typed source/intent requests, centralizes cooldown/flood gating, and updates cron, gateway, agent, plugin runtime, docs, changelog, and tests.

Reproducibility: yes. A focused unit path can seed the cooldown helper or runner with five recent run starts, call the cron/system wake(..., { mode: "now" }) path from the PR, and observe the immediate intent being skipped as flood instead of bypassing deferral like manual.

Next step before merge
A narrow automated repair can update the manual wake-now intent and add a focused regression test on the PR branch.

Security
Cleared: No concrete security or supply-chain regression was found; the diff changes scheduler/runtime API code, docs, tests, and changelog without new dependencies, workflow permissions, secret handling, or downloaded code.

Review findings

  • [P2] Classify wake-now requests as manual intent — src/cron/service/timer.ts:1604
Review details

Best possible solution:

Keep the typed cooldown design, but classify explicit operator wake-now requests as intent: "manual" or otherwise give that source the same full exemption, with a focused regression test.

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

Yes. A focused unit path can seed the cooldown helper or runner with five recent run starts, call the cron/system wake(..., { mode: "now" }) path from the PR, and observe the immediate intent being skipped as flood instead of bypassing deferral like manual.

Is this the best way to solve the issue?

No. Centralized typed wake intent is the right direction, but using source: "manual" with intent: "immediate" is not the narrowest compatible fix because the new policy treats only intent: "manual" as fully non-deferred.

Full review comments:

  • [P2] Classify wake-now requests as manual intent — src/cron/service/timer.ts:1604
    wake(..., { mode: "now" }) is the explicit manual system-event wake path, but the PR sends source: "manual" with intent: "immediate". In the new cooldown helper, immediate still goes through the flood guard, so after five recent runs this request can be skipped as flood and the queued system event may wait despite mode: "now". Use intent: "manual" for this path or give manual-source wake-now requests the same full exemption.
    Confidence: 0.88

Overall correctness: patch is incorrect
Overall confidence: 0.87

Acceptance criteria:

  • pnpm test src/infra/heartbeat-cooldown.test.ts src/infra/heartbeat-wake.test.ts src/infra/heartbeat-runner.scheduler.test.ts src/cron/service/timer.test.ts src/cron/service.main-job-passes-heartbeat-target-last.test.ts src/gateway/server-cron.test.ts src/plugins/runtime/index.test.ts
  • pnpm exec oxfmt --check --threads=1 src/cron/service/timer.ts src/infra/heartbeat-cooldown.test.ts src/infra/heartbeat-runner.scheduler.test.ts
  • pnpm check:changed

What I checked:

Likely related people:

  • steipete: Recent GitHub history shows repeated current-main work across heartbeat runner, heartbeat wake, cron, and plugin-runtime-adjacent paths, including heartbeat runner fixes and cron pressure handling. (role: recent maintainer; confidence: high; commits: b4437047f477, 9180173f9a79, f5e7557c70c1; files: src/infra/heartbeat-runner.ts, src/infra/heartbeat-wake.ts, src/cron/service/timer.ts)
  • vignesh07: Feature history for heartbeat-wake.ts includes the queue-per-target wake machinery and cron reminder routing commits, both adjacent to the wake coalescing behavior this PR changes. (role: introduced behavior; confidence: medium; commits: 064a3079cb5f, f988abf202e3; files: src/infra/heartbeat-wake.ts, src/cron/service/timer.ts)
  • obviyus: Recent history includes the deferred cron heartbeat target preservation work that touched the same wake request/heartbeat override contract this PR updates. (role: adjacent owner; confidence: medium; commits: 1d4e4314dddc; files: src/infra/heartbeat-wake.ts, src/cron/service/timer.ts, src/gateway/server-cron.ts)
  • vincentkoc: Recent GitHub history shows multiple gateway hook and plugin runtime boundary changes near the affected hook/runtime surfaces. (role: recent maintainer; confidence: medium; commits: 7343d1b2ad1b, 6f38425e5c18, a494eea6d40f; files: src/plugins/runtime/runtime-system.ts, src/gateway/server/hooks.ts)

Remaining risk / open question:

  • No validation commands were run because this was a read-only review; the repair should rerun the targeted heartbeat/cron/plugin tests and changed gate before merge.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 6a54aac4892c.

Copy link
Copy Markdown

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

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: deba2340f9

ℹ️ 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".

Comment thread src/plugins/runtime/runtime-system.ts Outdated
const requestHeartbeatNow: PluginRuntime["system"]["requestHeartbeatNow"] = (opts) =>
requestHeartbeat({
source: opts?.source ?? "other",
intent: opts?.intent ?? "event",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve immediate semantics for requestHeartbeatNow alias

The deprecated runtime.system.requestHeartbeatNow() compatibility shim now defaults to intent: "event", which makes legacy callers defer behind nextDueMs instead of waking promptly. Before this change, requestHeartbeatNow effectively bypassed interval gating for non-interval reasons, so plugins relying on the old API to surface system events quickly will now be delayed until the next heartbeat tick unless they were updated to pass intent explicitly. For a deprecated compatibility alias, this is a behavior regression that can silently break existing plugins.

Useful? React with 👍 / 👎.

Comment thread src/cron/service/timer.ts
state.deps.enqueueSystemEvent(text);
if (opts.mode === "now") {
state.deps.requestHeartbeatNow({ reason: "wake" });
state.deps.requestHeartbeat({ source: "manual", intent: "immediate", reason: "wake" });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Classify manual wake-now requests as manual intent

wake(..., { mode: "now" }) is the explicit operator-triggered wake path, but it is tagged as intent: "immediate" instead of "manual". In the new cooldown policy, only manual is fully exempt from deferral, while immediate can still be dropped by the flood guard. This means repeated user-issued wake-now commands can be suppressed under burst conditions, which contradicts the intended non-deferred manual path.

Useful? React with 👍 / 👎.

@steipete steipete merged commit c6817d8 into main May 2, 2026
102 checks passed
@steipete steipete deleted the pr-75439-best branch May 2, 2026 13:52
@steipete
Copy link
Copy Markdown
Contributor Author

steipete commented May 2, 2026

Landed in c6817d8d7ab9c5b6cfea4f6ab0d16d9c249f58c9 after fixup on PR head 14b10e612abf191c74f7c0a9cdfcf43f196468ba.

Maintainer fixups:

  • Kept deprecated runtime.system.requestHeartbeatNow() as an immediate wake compatibility alias in src/plugins/runtime/runtime-system.ts.
  • Marked exec system events as untrusted in src/agents/bash-tools.exec-runtime.ts.
  • Resolved the final rebase overlap in src/cron/service/timer.ts to keep current-main retry behavior while preserving the structured heartbeat API.

Proof:

  • pnpm test src/cron/service/timer.regression.test.ts src/plugins/runtime/index.test.ts src/agents/bash-tools.exec-runtime.test.ts
  • pnpm exec oxfmt --check --threads=1 src/cron/service/timer.ts src/cron/service/timer.regression.test.ts src/plugins/runtime/runtime-system.ts src/plugins/runtime/index.test.ts src/agents/bash-tools.exec-runtime.ts src/agents/bash-tools.exec-runtime.test.ts
  • git diff --check
  • GitHub: OpenGrep Scan changed paths (precise) passed on 14b10e612abf191c74f7c0a9cdfcf43f196468ba; checks-node-core-runtime-infra passed on the final rerun before the rebase, then passed again on 14b10e612abf191c74f7c0a9cdfcf43f196468ba.

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

Labels

agents Agent runtime and tooling cli CLI command changes docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR plugin: file-transfer size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: heartbeat fires duplicate runs when external wake events (openclaw agent CLI) arrive during scheduled heartbeat

1 participant