Skip to content

fix(gateway): exclude heartbeat sender ID from session display name#66544

Open
suboss87 wants to merge 2 commits intoopenclaw:mainfrom
suboss87:fix/webchat-heartbeat-session-label
Open

fix(gateway): exclude heartbeat sender ID from session display name#66544
suboss87 wants to merge 2 commits intoopenclaw:mainfrom
suboss87:fix/webchat-heartbeat-session-label

Conversation

@suboss87
Copy link
Copy Markdown
Contributor

Summary

  • Filters out the internal "heartbeat" placeholder from the session display name fallback chain in buildGatewaySessionRow
  • Without this filter, the heartbeat delivery path's fallback sender ID ("heartbeat") flows into origin.label, which then becomes the session dropdown label in webchat

Root Cause

resolveHeartbeatSenderId (in targets.ts) returns "heartbeat" as a fallback when no real sender can be resolved. This ends up in the session entry's origin.label. When buildGatewaySessionRow computes displayName, it falls through entry.displayName -> groupName -> entry.label -> originLabel, and if all the earlier values are undefined, "heartbeat" becomes the visible session label in the webchat dropdown.

Fix

2-line change: check if origin.label is "heartbeat" before using it as originLabel in the display name fallback chain.

Closes #66533

@suboss87
Copy link
Copy Markdown
Contributor Author

@vincentkoc @mbelinky small display fix. The heartbeat fallback sender ID leaks into the webchat session dropdown label. 2-line filter in buildGatewaySessionRow.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 14, 2026

Greptile Summary

Filters the internal "heartbeat" sentinel string from origin.label in buildGatewaySessionRow's display-name fallback chain, preventing heartbeat-triggered sessions from showing the meaningless label "heartbeat" in the webchat session selector. The fix is correctly placed and minimal; the only minor concern is that the literal string is decoupled from its source in resolveHeartbeatSenderId (src/infra/outbound/targets.ts), which could silently drift on a future rename.

Confidence Score: 5/5

  • This PR is safe to merge — it is a targeted, correct fix with no logic or security concerns.
  • The only finding is a P2 style note about a hardcoded magic string; there are no logic bugs, data issues, or security concerns introduced.
  • No files require special attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/session-utils.ts
Line: 1134

Comment:
**Magic string coupled to sentinel in `targets.ts`**

The literal `"heartbeat"` here is the fallback sentinel returned by `resolveHeartbeatSenderId` in `src/infra/outbound/targets.ts` (lines 339 and 353: `return candidates[0] ?? "heartbeat"`). These two sites are not linked by a constant, so a rename of the sentinel in `targets.ts` would silently re-introduce the bug. The comment's "e.g." framing also hints that other internal IDs (`"cron-event"` etc.) might need the same treatment eventually. Consider extracting a shared constant or a small predicate:

```ts
// In targets.ts (or a shared constants file):
export const HEARTBEAT_FALLBACK_SENDER_ID = "heartbeat";

// In session-utils.ts:
const originLabel =
  origin?.label && origin.label !== HEARTBEAT_FALLBACK_SENDER_ID
    ? origin.label
    : undefined;
```

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

Reviews (1): Last reviewed commit: "fix(gateway): exclude heartbeat sender I..." | Re-trigger Greptile

Comment thread src/gateway/session-utils.ts Outdated
const originLabel = origin?.label;
// Exclude internal system sender IDs (e.g. "heartbeat") from the display
// name fallback chain so the webchat session selector stays meaningful.
const originLabel = origin?.label && origin.label !== "heartbeat" ? origin.label : undefined;
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 Magic string coupled to sentinel in targets.ts

The literal "heartbeat" here is the fallback sentinel returned by resolveHeartbeatSenderId in src/infra/outbound/targets.ts (lines 339 and 353: return candidates[0] ?? "heartbeat"). These two sites are not linked by a constant, so a rename of the sentinel in targets.ts would silently re-introduce the bug. The comment's "e.g." framing also hints that other internal IDs ("cron-event" etc.) might need the same treatment eventually. Consider extracting a shared constant or a small predicate:

// In targets.ts (or a shared constants file):
export const HEARTBEAT_FALLBACK_SENDER_ID = "heartbeat";

// In session-utils.ts:
const originLabel =
  origin?.label && origin.label !== HEARTBEAT_FALLBACK_SENDER_ID
    ? origin.label
    : undefined;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/session-utils.ts
Line: 1134

Comment:
**Magic string coupled to sentinel in `targets.ts`**

The literal `"heartbeat"` here is the fallback sentinel returned by `resolveHeartbeatSenderId` in `src/infra/outbound/targets.ts` (lines 339 and 353: `return candidates[0] ?? "heartbeat"`). These two sites are not linked by a constant, so a rename of the sentinel in `targets.ts` would silently re-introduce the bug. The comment's "e.g." framing also hints that other internal IDs (`"cron-event"` etc.) might need the same treatment eventually. Consider extracting a shared constant or a small predicate:

```ts
// In targets.ts (or a shared constants file):
export const HEARTBEAT_FALLBACK_SENDER_ID = "heartbeat";

// In session-utils.ts:
const originLabel =
  origin?.label && origin.label !== HEARTBEAT_FALLBACK_SENDER_ID
    ? origin.label
    : undefined;
```

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

@suboss87 suboss87 force-pushed the fix/webchat-heartbeat-session-label branch from cb6a738 to 2d9bdc3 Compare April 16, 2026 01:09
@suboss87
Copy link
Copy Markdown
Contributor Author

Addressed the Greptile P2 about the magic string. Replaced the inline "heartbeat" literal with a named HEARTBEAT_FALLBACK_SENDER constant that references where the sentinel comes from (resolveHeartbeatSenderId in targets.ts).

The heartbeat delivery path uses "heartbeat" as a fallback sender ID
(resolveHeartbeatSenderId). This flows into origin.label on the session
entry, and buildGatewaySessionRow uses it as the last-resort display name.
Over time this causes the webchat session selector to show "heartbeat"
instead of the actual session label.

Filter out the internal "heartbeat" placeholder from the originLabel
fallback so the session dropdown stays meaningful.

Closes openclaw#66533
@suboss87 suboss87 force-pushed the fix/webchat-heartbeat-session-label branch from cc6c53e to bb3bc39 Compare April 19, 2026 00:29
Copy link
Copy Markdown

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

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: bb3bc39526

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Matches the fallback sender ID in resolveHeartbeatSenderId (targets.ts).
const HEARTBEAT_FALLBACK_SENDER = "heartbeat";
const originLabel =
origin?.label && origin.label !== HEARTBEAT_FALLBACK_SENDER ? origin.label : undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope heartbeat-label suppression to synthetic events

This condition suppresses any origin.label === "heartbeat" regardless of where the label came from, but origin.label can also be a real conversation name (for direct chats it is derived from sender name/ID). In sessions that have no displayName or manual label, a legitimate contact/chat named "heartbeat" will now lose its human-readable name and fall back to a generic key-derived label. Filter by synthetic heartbeat provenance (for example system-event/provider context) instead of a global string match.

Useful? React with 👍 / 👎.

@prtags
Copy link
Copy Markdown

prtags Bot commented Apr 23, 2026

Related work from PRtags group smiling-wolf-gp52

Title: Open PR duplicate: [Bug]: WebChat session selector shows main session as “heartbeat” after a while, making /new fe…

Number Title
#66544* fix(gateway): exclude heartbeat sender ID from session display name
#66656 fix(gateway): skip heartbeat diagnostics in session preview

* This PR

@suboss87
Copy link
Copy Markdown
Contributor Author

@vincentkoc @mbelinky quick bump — 8 days since the last review, CI is green, Greptile P2 addressed (named constant). Two-line display fix, no logic change. Happy to split or adjust if anything looks off.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 1, 2026

Codex review: needs changes before merge.

Summary
The branch changes buildGatewaySessionRow so origin.label is ignored when it equals the heartbeat fallback sender string.

Reproducibility: yes. as a source-level reproduction: a session entry with no displayName or label and origin.label: "heartbeat" still returns displayName: "heartbeat" from buildGatewaySessionRow on current main. The PR branch also creates a regression path for a legitimate direct-origin label exactly named heartbeat.

Next step before merge
A repair worker can narrowly revise this small patch to add the provenance predicate, regression coverage, and changelog entry without changing product behavior.

Security
Cleared: The diff is limited to Gateway session display-name selection and does not touch CI, dependencies, scripts, package metadata, permissions, secrets, or code execution surfaces.

Review findings

  • [P2] Scope heartbeat suppression to synthetic provenance — src/gateway/session-utils.ts:1134-1136
  • [P3] Add the required changelog entry — src/gateway/session-utils.ts:1134-1136
Review details

Best possible solution:

Land a scoped Gateway row-projection fix that ignores only synthetic or stale heartbeat origin metadata, preserves real labels named heartbeat, covers list/live row callers with tests, and records the user-facing fix in CHANGELOG.md.

Do we have a high-confidence way to reproduce the issue?

Yes, as a source-level reproduction: a session entry with no displayName or label and origin.label: "heartbeat" still returns displayName: "heartbeat" from buildGatewaySessionRow on current main. The PR branch also creates a regression path for a legitimate direct-origin label exactly named heartbeat.

Is this the best way to solve the issue?

No. The shared row projection is the right surface, but a global string filter is overbroad; the safer fix suppresses only known synthetic or stale heartbeat origin metadata while preserving explicit and human-derived labels.

Full review comments:

  • [P2] Scope heartbeat suppression to synthetic provenance — src/gateway/session-utils.ts:1134-1136
    This suppresses every origin.label === "heartbeat", but direct session labels can come from SenderName or From. A legitimate contact/chat named heartbeat with no manual label or display name would lose its human-readable label, so gate the suppression on synthetic or stale heartbeat provenance instead of the raw string alone.
    Confidence: 0.87
  • [P3] Add the required changelog entry — src/gateway/session-utils.ts:1134-1136
    This is a user-facing Control UI/WebChat session selector fix, so repo policy requires a single-line CHANGELOG.md fix entry. Please add one crediting @suboss87.
    Confidence: 0.82

Overall correctness: patch is incorrect
Overall confidence: 0.86

Acceptance criteria:

  • pnpm test src/gateway/session-utils.test.ts src/gateway/server.sessions.list-changed.test.ts
  • pnpm exec oxfmt --check --threads=1 src/gateway/session-utils.ts src/gateway/session-utils.test.ts src/gateway/server.sessions.list-changed.test.ts CHANGELOG.md
  • pnpm check:changed

What I checked:

  • Current row fallback still uses origin.label: buildGatewaySessionRow assigns originLabel = origin?.label and uses it as the last display-name fallback after entry.displayName, group display name, and entry.label. (src/gateway/session-utils.ts:1448, a4c1c28a1731)
  • Direct-origin labels can be legitimate human labels: For direct chats, resolveConversationLabel returns SenderName or From, so the string heartbeat is not inherently synthetic when it appears in origin.label. (src/channels/conversation-label.ts:38, a4c1c28a1731)
  • Origin metadata stores resolved labels: deriveSessionOrigin persists the resolved conversation label on origin.label, which is the field later consumed by Gateway session row projection. (src/config/sessions/metadata.ts:63, a4c1c28a1731)
  • Current main guards new system-event origin writes: initSessionState passes skipSystemEventOrigin: isSystemEvent, and the metadata helper skips heartbeat, cron-event, and exec-event providers when that option is set. (src/auto-reply/reply/session.ts:669, a4c1c28a1731)
  • PR predicate is globally string-based: The PR patch suppresses every origin.label === "heartbeat" via a local constant, without checking origin provenance, session kind, provider, or stale synthetic metadata shape. (src/gateway/session-utils.ts:1134, bb3bc39526cf)
  • WebChat displays row label/displayName before key fallback: resolveSessionDisplayName returns row label or displayName before parsing a key fallback, so a Gateway row display name derived from stale origin.label remains visible in the selector. (ui/src/ui/chat/session-controls.ts:420, a4c1c28a1731)

Likely related people:

  • vincentkoc: Current available history shows Vincent Koc as the recent author on src/gateway/session-utils.ts, and the PR author explicitly routed this Gateway/session display review to @vincentkoc. (role: recent maintainer; confidence: medium; commits: 882ddc4665d5, 95001d6c41c1; files: src/gateway/session-utils.ts, src/config/sessions/metadata.ts, src/auto-reply/reply/session.ts)
  • mbelinky: The current changelog credits @mbelinky for Gateway/sessions: preserve shared session route on system events #66073, the closely related Gateway/session fix that prevents synthetic heartbeat origin metadata from poisoning shared sessions, and the PR author also asked @mbelinky to review this area. (role: adjacent owner; confidence: medium; files: CHANGELOG.md, src/auto-reply/reply/session.ts, src/config/sessions/metadata.ts)

Remaining risk / open question:

  • The correct stale/synthetic heartbeat predicate needs focused regression coverage so read-side hardening does not hide legitimate direct labels named heartbeat.

Codex review notes: model gpt-5.5, reasoning high; reviewed against a4c1c28a1731.

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

Labels

gateway Gateway runtime size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: WebChat session selector shows main session as “heartbeat” after a while, making /new feel like sessions disappear

1 participant