fix(session): prevent heartbeat/cron/exec events from triggering session reset (#58409)#58605
Conversation
…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 SummaryThis 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 Key observations:
Confidence Score: 5/5Safe 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 AIThis 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 |
| const isSystemEvent = | ||
| ctx.Provider === "heartbeat" || ctx.Provider === "cron-event" || ctx.Provider === "exec-event"; |
There was a problem hiding this 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.)
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, | ||
| }, | ||
| }; |
There was a problem hiding this 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
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.There was a problem hiding this comment.
💡 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".
| const freshEntry = entry | ||
| ? evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy }).fresh | ||
| ? isSystemEvent | ||
| ? true | ||
| : evaluateSessionFreshness({ updatedAt: entry.updatedAt, now, policy: resetPolicy }).fresh |
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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:
- Skip
updatedAtwrites for system events — guard theupdatedAt: Date.now()assignment with the sameisSystemEventcheck, or add a separatelastUserInteractionAtfield for freshness evaluation. - 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
left a comment
There was a problem hiding this comment.
LGTM — reviewed code, tests, and approach. Clean implementation.
…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.
…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.
…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.
…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.
…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.
…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.
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:
Root Cause
In
src/auto-reply/reply/session.ts, theinitSessionStatefunction 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 forcefreshEntry=trueto skip reset policy evaluation for these automated events.Code Changes
Testing
Added comprehensive test coverage in
src/auto-reply/reply/session.heartbeat-no-reset.test.ts:All tests pass:
Impact
Verification
To verify the fix works:
Enable heartbeat with short interval:
Set aggressive idle reset:
Have an active conversation, then wait for heartbeat to trigger
Verify session is NOT reset (context preserved)
Related Issues