Skip to content

fix(session): prevent heartbeat/cron/exec events from triggering session reset (#58409)#58605

Merged
fabianwilliams merged 1 commit into
openclaw:mainfrom
Linux2010:fix/58409-heartbeat-session-reset
Apr 1, 2026
Merged

fix(session): prevent heartbeat/cron/exec events from triggering session reset (#58409)#58605
fabianwilliams merged 1 commit into
openclaw:mainfrom
Linux2010:fix/58409-heartbeat-session-reset

Conversation

@Linux2010

Copy link
Copy Markdown
Contributor

Summary

Fixes #58409 - Heartbeat system causes silent session reset leading to user data loss.

Problem

The heartbeat system (and similar automated events like cron-event and exec-event) triggered the session initialization logic, which evaluated session freshness based on idle/daily reset policies. When a session was considered 'stale' (e.g., idle for more than the configured timeout), it was silently reset, causing:

  • Complete loss of conversation context (8,000+ messages in reported case)
  • 1.2MB+ of data moved to .reset backup files
  • No warning to users
  • No automatic recovery mechanism

Root Cause

In src/auto-reply/reply/session.ts, the initSessionState function evaluates session freshness for ALL requests, including automated system events. The freshness check uses idle/daily reset policies that are appropriate for user interactions but should NOT apply to system-initiated check-ins.

Solution

Detect system event providers (heartbeat, cron-event, exec-event) and force freshEntry=true to skip reset policy evaluation for these automated events.

Code Changes

// Before
const freshEntry = entry
  ? evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy }).fresh
  : false;

// After
const isSystemEvent = ctx.Provider === "heartbeat" || ctx.Provider === "cron-event" || ctx.Provider === "exec-event";
const freshEntry = entry
  ? isSystemEvent
    ? true
    : evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy }).fresh
  : false;

Testing

Added comprehensive test coverage in src/auto-reply/reply/session.heartbeat-no-reset.test.ts:

  • ✅ Heartbeat provider does NOT trigger session reset
  • ✅ Regular provider DOES trigger reset when session is stale (expected behavior preserved)
  • ✅ Daily reset mode does NOT affect heartbeat runs
  • ✅ Cron-event provider does NOT trigger reset
  • ✅ Exec-event provider does NOT trigger reset

All tests pass:

✓ src/auto-reply/reply/session.heartbeat-no-reset.test.ts (5 tests) 270ms

Impact

  • Users with heartbeat enabled: No longer at risk of silent data loss
  • Cron job users: Automated jobs preserve session continuity
  • Exec event tracking: Command completion events don't disrupt sessions
  • Regular users: No change in behavior - idle/daily resets still work as expected

Verification

To verify the fix works:

  1. Enable heartbeat with short interval:

    openclaw config set agents.defaults.heartbeat '{"every":"5m"}' --json
  2. Set aggressive idle reset:

    openclaw config set session.reset '{"mode":"idle","idleMinutes":2}' --json
  3. Have an active conversation, then wait for heartbeat to trigger

  4. Verify session is NOT reset (context preserved)

Related Issues

…ion reset

Fixes openclaw#58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
@greptile-apps

greptile-apps Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a data-loss bug (#58409) where heartbeat, cron-event, and exec-event system providers were incorrectly passing through the session freshness evaluation, causing silent session resets and loss of conversation context for users with aggressive idle/daily reset policies.

The fix is minimal and correct: a new isSystemEvent flag bypasses evaluateSessionFreshness (forcing freshEntry=true) when the request originates from one of the three known automated system providers. All three provider values match exactly what heartbeat-runner.ts sets at runtime (line 660), so the logic is sound. The new test file covers all five relevant scenarios thoroughly.

Key observations:

  • The hardcoded provider strings in session.ts duplicate knowledge from heartbeat-runner.ts with no shared constant; a future new system event provider type could silently miss the reset guard if only one file is updated.
  • The comment on the resetTriggered assertion in the test for the regular-provider reset case is slightly misleading (see inline comment).
  • No behavioral change for regular user-initiated messages — idle and daily resets continue to work as expected.

Confidence Score: 5/5

Safe to merge — the change is small, well-targeted, and the test coverage is comprehensive.

Both findings are P2 style/maintainability suggestions that do not affect correctness or runtime behaviour. The fix itself directly addresses the root cause, the three provider strings are exhaustive given the current codebase, and the new tests validate both the fixed path and the preserved regular-reset path.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/session.ts
Line: 390-391

Comment:
**Consider a shared constant for system event providers**

The provider strings `"heartbeat"`, `"cron-event"`, and `"exec-event"` are defined/assigned in `src/infra/heartbeat-runner.ts` (line 660) and now also checked here. Because this is a plain string comparison with no shared source of truth, adding a new system event provider type in `heartbeat-runner.ts` might silently leave `session.ts` out of sync — the reset protection would not apply to the new provider.

A small shared constant (e.g. `SYSTEM_EVENT_PROVIDERS = new Set(["heartbeat", "cron-event", "exec-event"])` exported from a common location and imported by both files) would make this coupling explicit and easier to extend.

(No change to runtime behavior — just flagging the maintainability gap for consideration.)

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/auto-reply/reply/session.heartbeat-no-reset.test.ts
Line: 110

Comment:
**Misleading comment on `resetTriggered`**

The comment `// Not a manual reset, but idle reset` is slightly misleading. An idle-triggered reset (staleness check) also leaves `resetTriggered` as `false``resetTriggered` tracks explicit user commands like `/reset` or `/new`, not automated policy-based resets. The comment could cause confusion for future readers. Consider clarifying it to: `// resetTriggered tracks only explicit /reset or /new commands, not idle policy resets`

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

Reviews (1): Last reviewed commit: "fix(session): prevent heartbeat/cron/exe..." | Re-trigger Greptile

Comment on lines +390 to +391
const isSystemEvent =
ctx.Provider === "heartbeat" || ctx.Provider === "cron-event" || ctx.Provider === "exec-event";

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 Consider a shared constant for system event providers

The provider strings "heartbeat", "cron-event", and "exec-event" are defined/assigned in src/infra/heartbeat-runner.ts (line 660) and now also checked here. Because this is a plain string comparison with no shared source of truth, adding a new system event provider type in heartbeat-runner.ts might silently leave session.ts out of sync — the reset protection would not apply to the new provider.

A small shared constant (e.g. SYSTEM_EVENT_PROVIDERS = new Set(["heartbeat", "cron-event", "exec-event"]) exported from a common location and imported by both files) would make this coupling explicit and easier to extend.

(No change to runtime behavior — just flagging the maintainability gap for consideration.)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/session.ts
Line: 390-391

Comment:
**Consider a shared constant for system event providers**

The provider strings `"heartbeat"`, `"cron-event"`, and `"exec-event"` are defined/assigned in `src/infra/heartbeat-runner.ts` (line 660) and now also checked here. Because this is a plain string comparison with no shared source of truth, adding a new system event provider type in `heartbeat-runner.ts` might silently leave `session.ts` out of sync — the reset protection would not apply to the new provider.

A small shared constant (e.g. `SYSTEM_EVENT_PROVIDERS = new Set(["heartbeat", "cron-event", "exec-event"])` exported from a common location and imported by both files) would make this coupling explicit and easier to extend.

(No change to runtime behavior — just flagging the maintainability gap for consideration.)

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

updatedAt: staleTime,
systemSent: true,
},
};

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 Misleading comment on resetTriggered

The comment // Not a manual reset, but idle reset is slightly misleading. An idle-triggered reset (staleness check) also leaves resetTriggered as falseresetTriggered tracks explicit user commands like /reset or /new, not automated policy-based resets. The comment could cause confusion for future readers. Consider clarifying it to: // resetTriggered tracks only explicit /reset or /new commands, not idle policy resets

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/session.heartbeat-no-reset.test.ts
Line: 110

Comment:
**Misleading comment on `resetTriggered`**

The comment `// Not a manual reset, but idle reset` is slightly misleading. An idle-triggered reset (staleness check) also leaves `resetTriggered` as `false``resetTriggered` tracks explicit user commands like `/reset` or `/new`, not automated policy-based resets. The comment could cause confusion for future readers. Consider clarifying it to: `// resetTriggered tracks only explicit /reset or /new commands, not idle policy resets`

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

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

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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b30579f9fd

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

Comment on lines 392 to +395
const freshEntry = entry
? evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy }).fresh
? isSystemEvent
? true
: evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy }).fresh

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 Badge Preserve reset staleness for system-triggered turns

Forcing freshEntry to true for heartbeat/cron-event/exec-event here means these automated turns skip reset evaluation and then continue through normal persistence, which updates sessionEntry.updatedAt later in initSessionState. In idle/daily reset modes, a periodic heartbeat or cron run will keep advancing updatedAt, so the next real user message is no longer considered stale and the configured reset policy is effectively bypassed indefinitely while system events keep arriving.

Useful? React with 👍 / 👎.

@sharkqwy sharkqwy left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

updatedAt side-effect: heartbeats silently suppress all idle resets

The fix correctly prevents system events from triggering resets themselves, but there's a subtle downstream issue:

Line ~482 always sets updatedAt: Date.now() on the persisted session entry — including for heartbeat/cron/exec runs. This means every heartbeat bumps updatedAt, so the next real user message also evaluates freshEntry=true (because updatedAt is only minutes old, not hours).

Net effect: With heartbeat enabled, the idle-reset policy effectively never fires. A user could go AFK for days, but because heartbeats keep refreshing updatedAt every 5-30 minutes, the session is never considered stale when the user finally returns.

This may be intentional (heartbeat users probably want session continuity), but it's worth calling out because it's a semantic change beyond what the PR description says. If the intent is to preserve idle reset for user messages while only skipping it for system events, consider either:

  1. Skip updatedAt writes for system events — guard the updatedAt: Date.now() assignment with the same isSystemEvent check, or add a separate lastUserInteractionAt field for freshness evaluation.
  2. Document the behavior — if heartbeat-keeps-session-alive is the desired behavior, the PR description should note that enabling heartbeat effectively disables idle session reset.

Minor: the isSystemEvent check could use a Set or helper (e.g. const SYSTEM_PROVIDERS = new Set(["heartbeat", "cron-event", "exec-event"])) to make it easier to extend when new system event types are added.

@fabianwilliams fabianwilliams left a comment

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.

LGTM — reviewed code, tests, and approach. Clean implementation.

@fabianwilliams
fabianwilliams merged commit 6c3eea3 into openclaw:main Apr 1, 2026
40 of 42 checks passed
steipete pushed a commit to Mlightsnow/openclaw that referenced this pull request Apr 1, 2026
…ion reset (openclaw#58605)

Fixes openclaw#58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…ion reset (openclaw#58605)

Fixes openclaw#58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…ion reset (openclaw#58605)

Fixes openclaw#58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…ion reset (openclaw#58605)

Fixes openclaw#58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…ion reset (openclaw#58605)

Fixes openclaw#58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
Nachx639 pushed a commit to Nachx639/clawdbot that referenced this pull request Jun 17, 2026
…ion reset (openclaw#58605)

Fixes openclaw#58409 - Heartbeat system causes silent session reset leading to user data loss.

The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.

Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior

This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
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.

[Bug]: [CRITICAL] Heartbeat System Causes Silent Session Reset - User Data Loss

3 participants