openclaw - ✅(Solved) Fix [BUG] HEARTBEAT.md still pollutes main session with regular heartbeat and cron jobs targeting main (v2026.4.2) [2 pull requests, 3 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#60080Fetched 2026-04-08 02:36:39
View on GitHub
Comments
3
Participants
1
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
commented ×3cross-referenced ×2

Root Cause

PR #53152 added suppression logic that checks whether the session key matches the pattern agent:*:cron:*. This logic only triggers when:

  1. A dedicated cron agent exists (with id: "cron" in agents.list), creating sessions like agent:cron:<channel>:<key>, OR
  2. Cron jobs inherently create sessions with keys containing :cron:

In this installation:

  • ❌ No cron agent is configured
  • ❌ Cron jobs with sessionTarget: "main" reuse the main session → session key is agent:main:main (no cron segment)
  • ❌ Regular heartbeat runs use agent:main:main directly; heartbeat is not cron-triggered

Result: The suppression condition (sessionKey matches agent:*:cron:*) is never true, so HEARTBEAT.md is always injected.

Fix Action

Workaround

{
  "agents": {
    "defaults": {
      "heartbeat": { "every": "30m", "target": "isolated" }
    }
  },
  "cron": {
    "jobs": [{ "sessionTarget": "isolated" }]
  }
}

Or create a dedicated cron agent:

openclaw agent create cron --name "Cron Runner"

PR fix notes

PR #60555: fix(agents): backfill missing sessionKey in embedded PI runner — prevents undefined key in model selection and live-switch

Description (problem / solution / changelog)

What this fixes (plain English)

When a chat session was started with only a session ID (no session key), downstream features like hooks, live-model switching, and error formatting would break because they expected a session key. This fix resolves the session key from the store at startup, so all downstream paths work correctly.

Technical details

Root cause: The embedded runner could receive a sessionId without a corresponding sessionKey. Downstream paths (hooks, live-model switch, error formatting, pending-clear) all require sessionKey but received undefined.

Fix: Added resolveSessionKeyForRequest() backfill at the start of the embedded runner when only sessionId is provided. The resolved key is used consistently throughout the run lifecycle. Logs a warning if backfill resolution fails (instead of silently swallowing).

Files changed:

  • src/agents/pi-embedded-runner/run.ts — backfill sessionKey from sessionId via resolver
  • src/agents/command/session.ts — updated docstring
  • src/agents/pi-embedded-runner.e2e.test.ts — e2e regression test for sessionKey backfill
  • src/gateway/sessions-history-http.test.ts — SSE seq validation: verifies NO_REPLY messages are excluded from streamed history and seq numbering is correct
  • CHANGELOG.md — release note for the sessionKey backfill fix

Related

  • Fixes #60552
  • Related: #29203 (hooks missing sessionKey), #52390 (missing sessionKey in message hook path), #58083 (cron jobs persisting wrong sessionKey)

Test plan

  • 60/60 tests pass (model + tool-result-context-guard suites)
  • Regression test: successful sessionId -> sessionKey backfill
  • Regression test: warning log emission when backfill resolution throws
  • SSE seq validation test for NO_REPLY message filtering in session history

Changed files

  • CHANGELOG.md (modified, +1/-1)
  • src/agents/command/session.resolve-session-key.test.ts (modified, +30/-1)
  • src/agents/command/session.ts (modified, +35/-3)
  • src/agents/pi-embedded-runner.e2e.test.ts (modified, +188/-1)
  • src/agents/pi-embedded-runner/run.ts (modified, +63/-6)
  • src/gateway/sessions-history-http.test.ts (modified, +20/-4)

PR #61803: fix(agents): heartbeat always targets main session — prevent routing to active subagent sessions

Description (problem / solution / changelog)

What this fixes (plain English)

When background heartbeat checks ran while sub-agents were active, they could accidentally target a sub-agent's session instead of the main one — corrupting the sub-agent's task results by overwriting them with a heartbeat status. This fix ensures heartbeats always route to the main session.

Technical details

Root cause: resolveHeartbeatSession() had three code paths where a subagent session key could slip through:

  1. Via opts.sessionKey (forced session key)
  2. Via heartbeat.session config value
  3. Via post-canonicalization (where canonicalization transforms a key into a subagent format)

Fix: Added isSubagentSessionKey() guards to all three paths in resolveHeartbeatSession. Any subagent session key is now stripped at every entry point, falling back to mainSessionKey.

Files changed:

  • src/infra/heartbeat-runner.ts — import isSubagentSessionKey, add guards to all three resolution paths, simplify agent ID resolution
  • src/infra/heartbeat-runner.returns-default-unset.test.ts — parameterized regression test covering both entry vectors

Related

  • Fixes #58878
  • Recreated from clean branch (previous PR #61526 closed as dirty)
  • Parent initiative: #25592
  • Companion PRs: #61829, #61463, #61801

Test plan

  • 32/32 tests pass
  • Subagent session keys via opts.sessionKey are rejected -> falls back to mainSessionKey
  • Subagent session keys via heartbeat.session config are rejected -> falls back to mainSessionKey
  • Post-canonicalization subagent keys are rejected
  • Legitimate main session keys still route correctly

Changed files

  • CHANGELOG.md (modified, +1/-1)
  • src/infra/heartbeat-runner.returns-default-unset.test.ts (modified, +92/-0)

Code Example

{
  "agents": {
    "defaults": {
      "heartbeat": { "every": "30m" }
    },
    "list": [
      {
        "id": "main",
        "name": "虾捣鼓"
        // No explicit heartbeat.isolatedSession or target set — uses default target:"last"
      }
    ]
  },
  "cron": {
    "enabled": true,
    "jobs": [
      {
        "id": "8cfc2ade-aee9-4cd9-b0fd-81f46e7b5d98",
        "name": "agent-analysis",
        "sessionTarget": "main"
      },
      {
        "id": "893bc03b-fc41-466c-bc07-4217d85d7f52",
        "name": "daily-report",
        "sessionTarget": "main"
      }
    ]
  }
}

---

Read HEARTBEAT.md if it exists (workspace context). Follow it strictly...

---

{
  "agents": {
    "defaults": {
      "heartbeat": { "every": "30m", "target": "isolated" }
    }
  },
  "cron": {
    "jobs": [{ "sessionTarget": "isolated" }]
  }
}

---

openclaw agent create cron --name "Cron Runner"
RAW_BUFFERClick to expand / collapse

Bug Description

HEARTBEAT.md content continues to be injected as user messages into the main session despite the fix in #53152 (v2026.3.24-beta.2). This affects both:

  1. Regular agent heartbeat (non-cron triggered) running at every: "30m"
  2. Cron jobs that have sessionTarget: "main" (directly targeting the main session)

Related

  • #53152 — partial fix only covers cron-triggered runs with sessionKey matching agent:*:cron:* pattern
  • #49878 — original heartbeat cache bug (different root cause: session persistence)

Environment

OpenClaw version: 2026.4.2 (d74a122)

Configuration (relevant parts):

{
  "agents": {
    "defaults": {
      "heartbeat": { "every": "30m" }
    },
    "list": [
      {
        "id": "main",
        "name": "虾捣鼓"
        // No explicit heartbeat.isolatedSession or target set — uses default target:"last"
      }
    ]
  },
  "cron": {
    "enabled": true,
    "jobs": [
      {
        "id": "8cfc2ade-aee9-4cd9-b0fd-81f46e7b5d98",
        "name": "agent-analysis",
        "sessionTarget": "main"
      },
      {
        "id": "893bc03b-fc41-466c-bc07-4217d85d7f52",
        "name": "daily-report",
        "sessionTarget": "main"
      }
    ]
  }
}

Note: There is no dedicated cron agent configured ("id": "cron" is absent from agents.list).

Observed Behavior

Main session polluted with HEARTBEAT.md system prompts

The main session file (~/.openclaw/agents/main/sessions/a5cf651a-*.jsonl) contains 9 instances of the heartbeat system prompt as persistent user messages:

Read HEARTBEAT.md if it exists (workspace context). Follow it strictly...

Injection timestamps (~30-minute intervals, matching heartbeat cycle):

#Timestamp (UTC)Beijing Time
12026-04-01 20:13:5104:13 (next day)
22026-04-02 00:13:5008:13
32026-04-02 07:05:3915:05
42026-04-02 18:00:3402:00 (next day)
52026-04-02 18:00:4702:00
62026-04-02 19:31:2203:31
72026-04-02 22:05:0306:05
82026-04-02 22:35:4106:35
92026-04-03 00:07:3008:07

Each injection is a role: user message that persists in the main conversation history.

Cron jobs running in main session

Both agent-analysis (runs 02:00 daily) and daily-report (runs 08:30 daily) execute in the main session (agent:main:main) rather than a dedicated cron session.

Root Cause

PR #53152 added suppression logic that checks whether the session key matches the pattern agent:*:cron:*. This logic only triggers when:

  1. A dedicated cron agent exists (with id: "cron" in agents.list), creating sessions like agent:cron:<channel>:<key>, OR
  2. Cron jobs inherently create sessions with keys containing :cron:

In this installation:

  • ❌ No cron agent is configured
  • ❌ Cron jobs with sessionTarget: "main" reuse the main session → session key is agent:main:main (no cron segment)
  • ❌ Regular heartbeat runs use agent:main:main directly; heartbeat is not cron-triggered

Result: The suppression condition (sessionKey matches agent:*:cron:*) is never true, so HEARTBEAT.md is always injected.

Expected Behavior

HEARTBEAT.md should never be injected as a persistent user message into interactive user sessions (main, DM, thread). Heartbeat system prompts should only appear in isolated sessions or be suppressed entirely.

Workaround

{
  "agents": {
    "defaults": {
      "heartbeat": { "every": "30m", "target": "isolated" }
    }
  },
  "cron": {
    "jobs": [{ "sessionTarget": "isolated" }]
  }
}

Or create a dedicated cron agent:

openclaw agent create cron --name "Cron Runner"

Additional Context

  • openclaw.json has "cron": { "enabled": true } but no cron agent exists
  • #53152 fix mentions "even when they target non-cron session keys" but the implementation only checks session key pattern, not trigger origin
  • LCM file confirms #53152 was included in 2026.3.24; 2026.4.2 changelog shows no additional heartbeat/cron fixes
  • Issue #49878 is a different bug (HEARTBEAT.md cached across gateway restarts due to persistent heartbeat sessions)

extent analysis

TL;DR

The most likely fix is to configure the heartbeat to target an isolated session or create a dedicated cron agent to prevent HEARTBEAT.md injection into the main session.

Guidance

  • Configure the heartbeat to target an isolated session by setting "target": "isolated" in the heartbeat defaults.
  • Create a dedicated cron agent using openclaw agent create cron --name "Cron Runner" to isolate cron jobs from the main session.
  • Update cron jobs to target the isolated session by setting "sessionTarget": "isolated" in the cron jobs configuration.
  • Verify that the HEARTBEAT.md injection stops by checking the main session file for new instances of the heartbeat system prompt.

Example

{
  "agents": {
    "defaults": {
      "heartbeat": { "every": "30m", "target": "isolated" }
    }
  },
  "cron": {
    "jobs": [{ "sessionTarget": "isolated" }]
  }
}

Notes

The provided workaround configurations should prevent HEARTBEAT.md injection into the main session. However, the root cause is related to the missing cron agent and the implementation of the suppression logic in PR #53152.

Recommendation

Apply the workaround by configuring the heartbeat to target an isolated session or creating a dedicated cron agent, as this directly addresses the issue and prevents HEARTBEAT.md injection into the main session.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [BUG] HEARTBEAT.md still pollutes main session with regular heartbeat and cron jobs targeting main (v2026.4.2) [2 pull requests, 3 comments, 1 participants]