Skip to content

fix(heartbeat): preserve session displayName during heartbeat polls#42584

Closed
rstar327 wants to merge 1 commit into
openclaw:mainfrom
rstar327:fix-heartbeat-display-name
Closed

fix(heartbeat): preserve session displayName during heartbeat polls#42584
rstar327 wants to merge 1 commit into
openclaw:mainfrom
rstar327:fix-heartbeat-display-name

Conversation

@rstar327

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Heartbeat polls overwrite the session's displayName to "heartbeat", breaking the control UI session selector
  • Why it matters: Users see generic "heartbeat" labels instead of descriptive session names set by actual messages
  • What changed: Pass the existing session label (entry.origin.label) as ConversationLabel in the heartbeat context, so deriveSessionOrigin preserves the existing label instead of falling back to the From field ("heartbeat")
  • What did NOT change (scope boundary): Heartbeat execution flow, sender resolution, delivery routing — only metadata derivation is affected

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

Session dropdown in the control UI now retains descriptive labels set by user messages instead of being overwritten to "heartbeat" every heartbeat interval.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Any
  • Channel: webchat (control UI)
  • Heartbeat config: agents.defaults.heartbeat.every: "30m"

Steps

  1. Configure an agent with heartbeat enabled
  2. Send messages — session dropdown shows descriptive labels
  3. Wait for heartbeat poll to fire
  4. Check session dropdown label

Expected

  • Session dropdown retains the descriptive label from user messages

Actual

  • Before: displayName overwritten to "heartbeat" after each heartbeat poll
  • After: displayName preserved from the existing session origin label

Evidence

Human Verification (required)

  • Verified scenarios: Heartbeat context now includes ConversationLabel: entry?.origin?.label which preserves existing label through resolveConversationLabelderiveSessionOrigin path
  • Edge cases checked: New sessions without existing label (entry?.origin?.label is undefined, falls back to From as before)
  • What you did not verify: End-to-end with live heartbeat polling

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert commit
  • Files/config to restore: src/infra/heartbeat-runner.ts
  • Known bad symptoms reviewers should watch for: Session labels not updating when they should (unlikely since this only preserves existing labels)

Risks and Mitigations

None — only preserves existing label, does not change label derivation logic.

@greptile-apps

greptile-apps Bot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a session displayName regression where heartbeat polls overwrote the human-readable session label with "heartbeat". The one-line change passes entry?.origin?.label as ConversationLabel in the heartbeat message context, so resolveConversationLabel returns the existing label immediately (before falling back to ctx.From) and deriveSessionOriginmergeOrigin propagates it unchanged into the session store. The fallback for brand-new sessions (no existing label) is preserved because entry?.origin?.label evaluates to undefined in that case.

The core logic is correct and well-scoped — only metadata derivation is touched. mergeOrigin correctly handles the preserved label: next.label equals the existing label, so the merge is a no-op in practice.

The one gap: no regression test was added for the fixed behaviour. The existing heartbeat test suite would be a natural home for a case that seeds an entry with a non-empty origin.label and asserts ConversationLabel is forwarded correctly.

Confidence Score: 4/5

  • Safe to merge — the fix is minimal, logically sound, and backward-compatible with no execution flow changes
  • The three-line change is correct and well-scoped. The downstream label derivation path correctly handles both "existing label preserved" and "no existing label, fallback to From" cases. The only gap preventing a score of 5 is the absence of a regression test for the fixed behavior, which would be a natural addition to the existing heartbeat test suite.
  • No files require special attention beyond the missing regression test case noted above

Last reviewed commit: a65757d

@@ -708,6 +708,9 @@ export async function runHeartbeatOnce(opts: {
MessageThreadId: delivery.threadId,
Provider: hasExecCompletion ? "exec-event" : hasCronEvents ? "cron-event" : "heartbeat",
SessionKey: sessionKey,
// Preserve the existing session label so heartbeat polls don't overwrite
// the user-facing displayName with "heartbeat" (#42495).
ConversationLabel: entry?.origin?.label,

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.

No test coverage for the fixed behavior.

The change is logically correct, but no regression test was added. The existing heartbeat test files (e.g. heartbeat-runner.ghost-reminder.test.ts, heartbeat-runner.sender-prefers-delivery-target.test.ts) demonstrate the pattern for unit-testing heartbeat context construction. A test case like the following would prevent regressions of issue #42495:

  • Seed a session entry with origin.label = "My Session"
  • Run runHeartbeatOnce (or build the context object directly)
  • Assert that ctx.ConversationLabel === "My Session"
  • Also assert that when entry.origin is undefined, ConversationLabel is undefined (so the From-based fallback still applies)

This mirrors the pattern used in src/channels/conversation-label.test.ts which already tests "prefers ConversationLabel when present".

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/heartbeat-runner.ts
Line: 713

Comment:
No test coverage for the fixed behavior.

The change is logically correct, but no regression test was added. The existing heartbeat test files (e.g. `heartbeat-runner.ghost-reminder.test.ts`, `heartbeat-runner.sender-prefers-delivery-target.test.ts`) demonstrate the pattern for unit-testing heartbeat context construction. A test case like the following would prevent regressions of issue #42495:

- Seed a session entry with `origin.label = "My Session"`
- Run `runHeartbeatOnce` (or build the context object directly)
- Assert that `ctx.ConversationLabel === "My Session"`
- Also assert that when `entry.origin` is `undefined`, `ConversationLabel` is `undefined` (so the `From`-based fallback still applies)

This mirrors the pattern used in `src/channels/conversation-label.test.ts` which already tests "prefers ConversationLabel when present".

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

@rstar327
rstar327 force-pushed the fix-heartbeat-display-name branch from a65757d to c28413a Compare March 11, 2026 00:24

@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: c28413ae28

ℹ️ 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 thread src/auto-reply/reply/agent-runner-execution.ts Outdated
@rstar327
rstar327 force-pushed the fix-heartbeat-display-name branch from 5401c6a to 5801112 Compare March 11, 2026 01:32
@steipete

Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already prevents heartbeat/cron/exec system events from rewriting session metadata, so this PR's ConversationLabel change is redundant.

What I checked:

  • System events are explicitly isolated: initSessionState classifies heartbeat, cron-event, and exec-event as system events and skips conversation rebinding for them. (src/auto-reply/reply/session.ts:258, 4737a86071e3)
  • Existing session state is preserved during system events: For system events, the session entry is rebuilt from baseEntry, preserving persisted fields such as displayName, and deriveSessionMetaPatch is invoked with skipSystemEventOrigin: isSystemEvent. (src/auto-reply/reply/session.ts:619, 4737a86071e3)
  • Origin derivation is disabled for heartbeat polls: deriveSessionOrigin returns undefined for heartbeat/cron-event/exec-event when skipSystemEventOrigin is set, so heartbeat turns do not replace the stored origin label/source metadata. (src/config/sessions/metadata.ts:58, 4737a86071e3)
  • Regression tests cover preserved heartbeat metadata: Tests assert that heartbeat does not synthesize origin on an empty session and preserves the existing persisted route/origin when heartbeat targets a different chat on the shared session. (src/auto-reply/reply/session.test.ts:3092, 4737a86071e3)
  • The PR's exact patch is not on main: Current runHeartbeatOnce still builds the heartbeat context without ConversationLabel, which shows the behavior was fixed elsewhere on main rather than by merging this branch. (src/infra/heartbeat-runner.ts:973, 7d9172d5e093)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review notes: reviewed against 7d9172d5e093; fix evidence: commit 4737a86071e3.

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.

Heartbeat polls overwrite session displayName, breaking control UI session selector

2 participants