Skip to content

fix: route system-event-triggered heartbeats to configured session#1542

Open
BingqingLyu wants to merge 12 commits into
mainfrom
fork-pr-56092-fix-heartbeat-session-routing
Open

fix: route system-event-triggered heartbeats to configured session#1542
BingqingLyu wants to merge 12 commits into
mainfrom
fork-pr-56092-fix-heartbeat-session-routing

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

System-event-triggered heartbeats (e.g. from exec notifyOnExit) were running in the originating session instead of the configured heartbeat.session. This caused heartbeat duties to execute inside active DM conversations, polluting the session with unrelated memory updates and email checks.

Root Cause

In resolveHeartbeatSession(), the forcedSessionKey (originating session) was checked before the configured heartbeat.session. When an exec completion system event triggered an immediate heartbeat, the originating DM session was passed as forcedSessionKey and always won over the user's explicit session config.

Fix

1. Session resolution priority reorder (heartbeat-runner.ts)

heartbeat.session config is now resolved before forcedSessionKey, so the user's explicit configuration always wins. When no heartbeat.session is configured, behavior falls back to forcedSessionKey as before.

2. Cross-session event migration (heartbeat-runner.ts)

When the resolved heartbeat session differs from the originating session, pending system events are migrated:

  • Events are drained from the originating session and re-enqueued in the heartbeat session
  • Session-scoped events (e.g. session-maintenance warnings) stay in the originating session — identified via contextKey
  • Migration uses skipDedup: true to bypass consecutive-duplicate suppression
  • Each migration run is tagged with a unique migrationId via the __migrated field on SystemEvent
  • The trusted flag is preserved through migration

3. Skip-restore path (heartbeat-runner.ts)

When a heartbeat is skipped after migration (e.g. empty HEARTBEAT.md), migrated events are restored to the originating session:

  • Only events matching the current run's migrationId are removed from the heartbeat session
  • Events that arrived concurrently are preserved
  • Pre-existing heartbeat events with matching contextKey+text are not confused with migrated events thanks to stable __migrated identity

4. Wake-trigger event tagging (multiple files)

System events are now tagged with contextKey at their source to enable migration classification:

  • bash-tools.exec-runtime.tscontextKey: "exec"
  • cli-runner/execute.tscontextKey: "cli:watchdog"
  • server-methods/system.tscontextKey: "system"
  • server-restart-sentinel.tscontextKey: "restart"
  • server/hooks.tscontextKey: "hook"
  • session-maintenance-warning.tscontextKey: "session-maintenance"

5. System event infrastructure (system-events.ts)

  • Added skipDedup?: boolean to SystemEventOptions — bypasses consecutive-duplicate guard for cross-session migration
  • Added __migrated?: string to SystemEvent and SystemEventOptions — stable per-run migration identity marker

Tests

New test file heartbeat-runner.session-routing.test.ts covering:

  • Configured session wins over originating DM session
  • System events migrate from originating to heartbeat session
  • Session-scoped events preserved in originating session
  • Falls back to forcedSessionKey when no session configured
  • Regular scheduled heartbeats unaffected
  • Migrated events restored on heartbeat skip
  • Concurrent events preserved during skip-restore
  • Dedup bypass for migrated events matching destination tail
  • Trusted flag preserved through migration
  • Stable identity prevents pre-existing/migrated event confusion
  • Scoped migration ID preserves prior-run events

Fixes openclaw#41165

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Telegram DMs can still land in agent:main:main, polluting heartbeat/main session after #40519

2 participants