Skip to content

infra: fix heartbeat directive preservation and global enablement#74471

Merged
steipete merged 6 commits intoopenclaw:mainfrom
Sekhar03:sekhar/infra-fixes
Apr 29, 2026
Merged

infra: fix heartbeat directive preservation and global enablement#74471
steipete merged 6 commits intoopenclaw:mainfrom
Sekhar03:sekhar/infra-fixes

Conversation

@Sekhar03
Copy link
Copy Markdown
Contributor

This PR addresses several infrastructure technical debt items and bugs:

Fixes

  • Heartbeat Directive Preservation: Fixed a regex bug in src/infra/heartbeat-runner.ts that stripped non-task instructions from HEARTBEAT.md.
  • Global Heartbeat Enablement: Fixed a logic error in src/infra/heartbeat-summary.ts that restricted heartbeats to the default agent even when global defaults were set.

Technical Debt

  • Logging Migration: Migrated windows-acl.ts, dotenv.ts, and temp-download.ts to the structured SubsystemLogger to reduce console noise and improve audit logs.

All changes verified with pnpm tsgo:core.

@Sekhar03 Sekhar03 requested a review from a team as a code owner April 29, 2026 16:25
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR fixes two heartbeat bugs (regex stripping of non-task directives; global default enablement) and migrates three modules from console.warn to SubsystemLogger. The logging migrations are clean; the heartbeat-summary.ts logic fix is correct.

  • The new regex in heartbeat-runner.ts stops at the first \n\n inside the tasks: block, so HEARTBEAT.md files that use blank lines between task entries will have raw task YAML leaked into the "Additional context" prompt section.
  • const logger in dotenv.ts is declared between two import groups rather than after all imports.

Confidence Score: 3/5

Hold for the heartbeat-runner regex edge case — blank-line-separated task entries will pollute the generated prompt with raw task YAML.

One P1 logic bug in the newly introduced regex: the `

` stop condition truncates the replacement inside a multi-entry tasks block, causing task definitions to survive into the "Additional context" section of the heartbeat prompt. The remaining changes (logging migrations, heartbeat-summary fix, dotenv import ordering) are low-risk P2 or clean.

src/infra/heartbeat-runner.ts — the updated regex on line 746

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/dotenv.ts
Line: 8-9

Comment:
**`const` declaration split between import statements**

`const logger = createSubsystemLogger("infra:dotenv")` is placed between two `import` groups (lines 6 and 9). While JS/TS hoists all `import` declarations so this works at runtime, the interleaved `const` will trip the `import/first` or `import/order` lint rules and is confusing to readers. Move the logger initialisation after all imports.

```suggestion
import { resolveRequiredHomeDir } from "./home-dir.js";

const logger = createSubsystemLogger("infra:dotenv");
```

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

---

This is a comment left during a code review.
Path: src/infra/heartbeat-runner.ts
Line: 746

Comment:
**Regex stops early when tasks block contains blank lines**

The `\n\n` alternative in the lookahead `(?=^[^\s]|\n\n|$)` causes the replacement to stop at the first blank line found *inside* the `tasks:` block. If `HEARTBEAT.md` uses blank lines between task entries (common in YAML-style lists), the remaining task entries are left in `directives` and end up appended to the prompt as "Additional context", which is the opposite of the intended behaviour.

For example, with a file like:
```
tasks:
  - name: task1
    prompt: do thing 1

  - name: task2
    prompt: do thing 2

Some global directive
```
The regex only removes through `do thing 1`, leaving `- name: task2 …` in `directives`. Consider dropping the `\n\n` alternative and relying solely on the `^[^\s]` anchor (next top-level key) to terminate the match.

```suggestion
          .replace(/^tasks:[\s\S]*?(?=^[^\s]|$(?![\s\S]))/m, "")
```

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

Reviews (1): Last reviewed commit: "logging: migrate dotenv and temp-downloa..." | Re-trigger Greptile

Comment thread src/infra/dotenv.ts Outdated
Comment on lines 8 to 9
const logger = createSubsystemLogger("infra:dotenv");
import { resolveRequiredHomeDir } from "./home-dir.js";
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.

P2 const declaration split between import statements

const logger = createSubsystemLogger("infra:dotenv") is placed between two import groups (lines 6 and 9). While JS/TS hoists all import declarations so this works at runtime, the interleaved const will trip the import/first or import/order lint rules and is confusing to readers. Move the logger initialisation after all imports.

Suggested change
const logger = createSubsystemLogger("infra:dotenv");
import { resolveRequiredHomeDir } from "./home-dir.js";
import { resolveRequiredHomeDir } from "./home-dir.js";
const logger = createSubsystemLogger("infra:dotenv");
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/dotenv.ts
Line: 8-9

Comment:
**`const` declaration split between import statements**

`const logger = createSubsystemLogger("infra:dotenv")` is placed between two `import` groups (lines 6 and 9). While JS/TS hoists all `import` declarations so this works at runtime, the interleaved `const` will trip the `import/first` or `import/order` lint rules and is confusing to readers. Move the logger initialisation after all imports.

```suggestion
import { resolveRequiredHomeDir } from "./home-dir.js";

const logger = createSubsystemLogger("infra:dotenv");
```

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

Comment thread src/infra/heartbeat-runner.ts Outdated
@@ -743,7 +743,7 @@ After completing all due tasks, reply HEARTBEAT_OK.`;

if (params.heartbeatFileContent) {
const directives = params.heartbeatFileContent
.replace(/^[\s\S]*?^tasks:[\s\S]*?(?=^[^\s]|^$)/m, "")
.replace(/^tasks:[\s\S]*?(?=^[^\s]|\n\n|$)/m, "")
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 Regex stops early when tasks block contains blank lines

The \n\n alternative in the lookahead (?=^[^\s]|\n\n|$) causes the replacement to stop at the first blank line found inside the tasks: block. If HEARTBEAT.md uses blank lines between task entries (common in YAML-style lists), the remaining task entries are left in directives and end up appended to the prompt as "Additional context", which is the opposite of the intended behaviour.

For example, with a file like:

tasks:
  - name: task1
    prompt: do thing 1

  - name: task2
    prompt: do thing 2

Some global directive

The regex only removes through do thing 1, leaving - name: task2 … in directives. Consider dropping the \n\n alternative and relying solely on the ^[^\s] anchor (next top-level key) to terminate the match.

Suggested change
.replace(/^tasks:[\s\S]*?(?=^[^\s]|\n\n|$)/m, "")
.replace(/^tasks:[\s\S]*?(?=^[^\s]|$(?![\s\S]))/m, "")
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/heartbeat-runner.ts
Line: 746

Comment:
**Regex stops early when tasks block contains blank lines**

The `\n\n` alternative in the lookahead `(?=^[^\s]|\n\n|$)` causes the replacement to stop at the first blank line found *inside* the `tasks:` block. If `HEARTBEAT.md` uses blank lines between task entries (common in YAML-style lists), the remaining task entries are left in `directives` and end up appended to the prompt as "Additional context", which is the opposite of the intended behaviour.

For example, with a file like:
```
tasks:
  - name: task1
    prompt: do thing 1

  - name: task2
    prompt: do thing 2

Some global directive
```
The regex only removes through `do thing 1`, leaving `- name: task2 …` in `directives`. Consider dropping the `\n\n` alternative and relying solely on the `^[^\s]` anchor (next top-level key) to terminate the match.

```suggestion
          .replace(/^tasks:[\s\S]*?(?=^[^\s]|$(?![\s\S]))/m, "")
```

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

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 29, 2026

Codex review: found issues before merge.

What this changes:

The PR updates HEARTBEAT.md task-block directive stripping, enables non-default agent heartbeats when only global heartbeat defaults are configured, and migrates several console warnings in dotenv, temp-download, and Windows ACL helpers to subsystem loggers.

Maintainer follow-up before merge:

This is an active contributor PR with a concrete review finding and a heartbeat contract decision for maintainers; the right next action is request changes or clarify the intended behavior, not a separate automated replacement PR.

Review findings:

  • [P2] Keep the heartbeat enablement tests in sync — src/infra/heartbeat-summary.ts:41-43
Review details

Best possible solution:

Revise the PR so heartbeat enablement, docs, and tests all agree on the intended defaults-only multi-agent contract, add focused coverage for preserving non-task HEARTBEAT.md directives around task blocks, and keep the logger migration separate and scoped to warning output behavior.

Full review comments:

  • [P2] Keep the heartbeat enablement tests in sync — src/infra/heartbeat-summary.ts:41-43
    The new defaults check makes isHeartbeatEnabledForAgent return true for non-default agents when agents.defaults.heartbeat exists and no per-agent heartbeat blocks are configured. The current test for that exact config still expects ops to be disabled, so this PR will fail the checked-in heartbeat test suite unless the intended contract and tests/docs are updated together.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.86

Acceptance criteria:

  • pnpm test src/infra/heartbeat-runner.returns-default-unset.test.ts
  • pnpm test src/infra/heartbeat-runner.isolated-key-stability.test.ts
  • pnpm tsgo:core

What I checked:

  • Existing enablement behavior: Current main enables explicitly configured heartbeat agents when any agents.list entry has heartbeat, otherwise it falls back to the default agent only. (src/infra/heartbeat-summary.ts:32, df0074768c97)
  • Existing test contract: The current defaults-only heartbeat test expects main enabled and ops disabled when agents.defaults.heartbeat is set but no per-agent heartbeat blocks exist; the PR's new defaults check would make ops enabled. (src/infra/heartbeat-runner.returns-default-unset.test.ts:317, df0074768c97)
  • Docs contract context: The heartbeat docs describe agents.defaults.heartbeat as global behavior, while also documenting that any per-agent heartbeat block restricts runs to those agents; this supports a contract decision but does not remove the need to update tests with the behavior change. Public docs: docs/gateway/heartbeat.md. (docs/gateway/heartbeat.md:117, df0074768c97)
  • PR patch surface: The provided PR diff changes five files only: heartbeat runner/summary plus logger migration in dotenv, temp-download, and windows-acl; it does not touch workflows, lockfiles, dependency declarations, package scripts, or release/publish metadata. (25b0f0d9da60)
  • Feature history: The local feature-history pass shows the heartbeat runner, summary, tests, docs, and touched logging helpers were introduced in the visible main history by be8c246, with current blame on the central heartbeat lines pointing to 587b537. (be8c24633aaa)
  • Checkout cleanliness: Read-only inspection left the checkout clean on main. (df0074768c97)

Likely related people:

  • steipete: Visible main history and blame show the heartbeat runner/summary/tests/docs and the touched infra/security helper files coming from recent commits authored by Peter Steinberger. (role: recent maintainer and current-main feature owner; confidence: high; commits: be8c24633aaa, 587b537b4745; files: src/infra/heartbeat-summary.ts, src/infra/heartbeat-runner.ts, src/infra/heartbeat-runner.returns-default-unset.test.ts)

Remaining risk / open question:

  • The intended product contract for defaults-only multi-agent heartbeat enablement needs maintainer confirmation because the docs and proposed behavior point toward global defaults, while the current test suite asserts default-agent-only behavior.

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

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 29, 2026

Codex review: found issues before merge.

What this changes:

The PR changes heartbeat directive stripping and default heartbeat enablement, and migrates warnings/errors in command auth, config defaults, runtime error handlers, dotenv, temp-download, and Windows ACL helpers to createSubsystemLogger.

Maintainer follow-up before merge:

This is an active contributor PR with concrete blocking findings plus a heartbeat contract decision; the next action is maintainer review/request-changes on this PR rather than a separate automated replacement fix.

Review findings:

  • [P2] Strip the full tasks block from heartbeat context — src/infra/heartbeat-runner.ts:746
  • [P2] Update the heartbeat enablement contract tests — src/infra/heartbeat-summary.ts:41-43
  • [P2] Keep fatal error details in console output — src/index.ts:103-112
Review details

Best possible solution:

Revise the PR so the task-block removal preserves non-task HEARTBEAT.md directives while consuming the complete tasks: block, align heartbeat docs and tests with the intended global-defaults contract, and preserve console-visible formatted error text when moving fatal runtime paths to subsystem logging.

Full review comments:

  • [P2] Strip the full tasks block from heartbeat context — src/infra/heartbeat-runner.ts:746
    With the multiline flag, the $ branch in (?=^[^\s]|\n\n|$) matches the end of the tasks: line, so this replacement can remove only tasks: and leave the YAML task entries in directives. Any HEARTBEAT.md with tasks then appends raw task definitions to Additional context instead of stripping them.
    Confidence: 0.97
  • [P2] Update the heartbeat enablement contract tests — src/infra/heartbeat-summary.ts:41-43
    The new defaults check makes isHeartbeatEnabledForAgent return true for every agent when agents.defaults.heartbeat exists and no per-agent heartbeat blocks are configured. The checked-in test for that exact config still expects ops to be disabled, so the PR will fail the heartbeat test suite until the intended contract and coverage are updated together.
    Confidence: 0.9
  • [P2] Keep fatal error details in console output — src/index.ts:103-112
    These handlers used to print formatUncaughtError(err) directly. createSubsystemLogger only renders message or consoleMessage in pretty and compact console modes, so passing the Error only as { error } makes CLI failures and uncaught exceptions print generic messages without the exception text or stack.
    Confidence: 0.88

Overall correctness: patch is incorrect
Overall confidence: 0.91

Acceptance criteria:

  • pnpm test src/infra/heartbeat-runner.returns-default-unset.test.ts
  • pnpm test src/infra/heartbeat-runner.isolated-key-stability.test.ts
  • pnpm tsgo:core
  • Add or run a focused runtime logging check if the fatal-handler logger migration stays in scope.

What I checked:

  • PR patch surface: Latest raw PR patch at head f34f5f8374209b7f741ae67f4dd466c544f10389 changes nine TypeScript files only; it does not touch workflows, lockfiles, dependency declarations, install/build scripts, or package publishing metadata. (f34f5f837420)
  • Current main heartbeat stripping: Current main still removes a tasks: block with the existing regex, so main has not already implemented the PR's intended directive-preservation change. (src/infra/heartbeat-runner.ts:746, 9d03cd15a9a0)
  • PR regex regression: The PR replacement uses (?=^[^\s]|\n\n|$) with the m flag. Because $ matches end-of-line in multiline mode, the match can end immediately after tasks:, leaving raw task YAML in the prompt's Additional context section. (src/infra/heartbeat-runner.ts:746, f34f5f837420)
  • Existing test contract: The checked-in defaults-only heartbeat test expects main enabled and ops disabled when agents.defaults.heartbeat exists but no per-agent heartbeat blocks are configured; the PR's new unconditional defaults branch changes that result. (src/infra/heartbeat-runner.returns-default-unset.test.ts:317, 9d03cd15a9a0)
  • Docs contract context: The docs say agents.defaults.heartbeat sets global heartbeat behavior and that any per-agent heartbeat block restricts heartbeats to those agents, so the behavior change may be right but needs tests/docs to agree. Public docs: docs/gateway/heartbeat.md. (docs/gateway/heartbeat.md:117, 9d03cd15a9a0)
  • Logger console behavior: createSubsystemLogger renders only the message or consoleMessage in pretty/compact console styles; metadata is not shown there, so passing { error } without formatted text removes visible exception details from normal CLI output. (src/logging/subsystem.ts:366, 9d03cd15a9a0)

Likely related people:

  • steipete: Visible main blame and file history for the heartbeat runner/summary/tests/docs and the touched logging/runtime/infra helpers point to Peter Steinberger as the maintainer most connected to this area. (role: recent maintainer and current-main feature owner; confidence: high; commits: beff88175e91, 9d03cd15a9a0; files: src/infra/heartbeat-runner.ts, src/infra/heartbeat-summary.ts, src/infra/heartbeat-runner.returns-default-unset.test.ts)

Remaining risk / open question:

  • The intended defaults-only multi-agent heartbeat contract needs maintainer confirmation because the docs and PR point toward global defaults, while the checked-in test suite currently asserts default-agent-only behavior.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 9d03cd15a9a0.

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations cli CLI command changes labels Apr 29, 2026
@steipete steipete force-pushed the sekhar/infra-fixes branch from d03cb66 to 0cf090b Compare April 29, 2026 16:41
@openclaw-barnacle openclaw-barnacle Bot removed cli CLI command changes commands Command implementations labels Apr 29, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 29, 2026

Codex review: found issues before merge.

What this changes:

The branch preserves HEARTBEAT.md context around structured tasks: blocks, applies agents.defaults.heartbeat to all agents unless per-agent heartbeat entries restrict scope, adds focused heartbeat tests, migrates three infra/security warning sites to subsystem logging, and adds a changelog entry.

Maintainer follow-up before merge:

This is an active implementation PR with a concrete patch-level finding and heartbeat behavior semantics to confirm; the next step is maintainer review or request-changes on this PR rather than a separate automated repair branch.

Review findings:

  • [P2] Preserve top-level bullets after heartbeat tasks — src/infra/heartbeat-runner.ts:718-724
Review details

Best possible solution:

Centralize or align task-block stripping with the heartbeat task parser so only recognized task entries are removed and all top-level non-task Markdown before or after tasks: is preserved, including bullet lists; keep the defaults-only multi-agent behavior covered by tests and leave the logger migration scoped to warning output.

Full review comments:

  • [P2] Preserve top-level bullets after heartbeat tasks — src/infra/heartbeat-runner.ts:718-724
    While inTasksBlock is true, the new stripper treats every trimmed.startsWith("-") line as task YAML. A HEARTBEAT.md that puts a normal Markdown bullet immediately after the tasks: block will have that directive removed from Additional context, even though the docs promise non-task content is preserved. End the block based on indentation or recognized task-entry shape instead of every top-level - line, and cover that case.
    Confidence: 0.86

Overall correctness: patch is incorrect
Overall confidence: 0.86

Acceptance criteria:

  • pnpm test src/infra/heartbeat-runner.returns-default-unset.test.ts
  • pnpm test src/infra/heartbeat-runner.isolated-key-stability.test.ts
  • pnpm tsgo:core

What I checked:

  • Current main still uses the old task stripping path: origin/main still builds Additional context by applying the existing regex directly to heartbeatFileContent; the branch's new helper is not present on current main. (src/infra/heartbeat-runner.ts:745, e420592bdfd9)
  • Docs contract preserves non-task heartbeat content: The heartbeat docs state that non-task content in HEARTBEAT.md is preserved and appended as additional context after the due-task list. Public docs: docs/gateway/heartbeat.md. (docs/gateway/heartbeat.md:423, e420592bdfd9)
  • PR helper strips all hyphen-leading lines while inside tasks: The latest PR head sets isTaskListItem = trimmed.startsWith("-") and skips those lines before it has ended the tasks block, so a normal top-level Markdown bullet immediately after tasks is dropped instead of preserved. (src/infra/heartbeat-runner.ts:718, 0cf090bc6801)
  • New test covers prose after tasks but not bullet-only directives: The new regression test verifies a plain sentence after the task block; it does not cover a top-level Markdown bullet after the block, which is the case the helper still strips. (src/infra/heartbeat-runner.returns-default-unset.test.ts:1437, 0cf090bc6801)
  • Security-sensitive surface check: The latest diff touches TypeScript source, a heartbeat test, and CHANGELOG.md only; it does not change workflows, dependency declarations, lockfiles, install/build scripts, downloaded artifacts, package publishing metadata, or permissions. (0cf090bc6801)
  • Feature-history routing: Current-main blame for the heartbeat runner, summary helper, tests, and heartbeat docs points to Peter Steinberger in the visible history, making that the strongest routing signal for maintainer review. (src/infra/heartbeat-runner.ts:746, dc5a85d6064d)

Likely related people:

  • steipete: Visible current-main blame and file history for the central heartbeat runner, summary helper, tests, docs, and touched infra helpers point to Peter Steinberger; the PR timeline also shows a maintainer force-push of the latest heartbeat fix commit. (role: recent maintainer and current-main feature owner; confidence: high; commits: dc5a85d6064d, 0cf090bc6801; files: src/infra/heartbeat-runner.ts, src/infra/heartbeat-summary.ts, src/infra/heartbeat-runner.returns-default-unset.test.ts)

Remaining risk / open question:

  • The accepted HEARTBEAT.md task-block grammar is still split between parseHeartbeatTasks and the new local stripping heuristic, so maintainers should confirm the exact block-ending rule while fixing this patch.

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

@neeravmakwana
Copy link
Copy Markdown
Contributor

Hi @Sekhar03 . How did you request a review from a team?

"Sekhar03 requested a review from a team as a code owner"

lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
…enclaw#74471)

* refactor(security): replace console.warn with structured logger in windows-acl

* infra: fix heartbeat directive preservation and global enablement

* logging: migrate dotenv and temp-download to subsystem logger

* logging: migrate command-auth, unhandled-rejections, and index to subsystem logger

* logging: migrate config defaults to subsystem logger

* fix(heartbeat): preserve heartbeat task context

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…enclaw#74471)

* refactor(security): replace console.warn with structured logger in windows-acl

* infra: fix heartbeat directive preservation and global enablement

* logging: migrate dotenv and temp-download to subsystem logger

* logging: migrate command-auth, unhandled-rejections, and index to subsystem logger

* logging: migrate config defaults to subsystem logger

* fix(heartbeat): preserve heartbeat task context

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
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.

3 participants