Skip to content

test(gateway): consolidate deriveSessionTitle tests into dedicated module#48942

Closed
JFWaskin wants to merge 2 commits into
openclaw:mainfrom
JFWaskin:codex/upstream-session-title-tests
Closed

test(gateway): consolidate deriveSessionTitle tests into dedicated module#48942
JFWaskin wants to merge 2 commits into
openclaw:mainfrom
JFWaskin:codex/upstream-session-title-tests

Conversation

@JFWaskin

@JFWaskin JFWaskin commented Mar 17, 2026

Copy link
Copy Markdown

Summary

This PR fixes an issue where heartbeat polls could overwrite explicit user-labeled session titles with transcript-derived titles like "heartbeat".

Changes

  • Extracts deriveSessionTitle to session-title.ts with a dedicated test module.
  • Preserves explicit session labels before transcript-derived fallbacks.
  • Keeps auto-populated origin.label behind firstUserMessage so unlabeled inbound DMs can still use transcript-derived titles.
  • Keeps session-utils.ts as a compatibility re-export site.
  • Clarifies the zero timestamp test name to describe the actual updatedAt: 0 input.

Title precedence

  1. displayName
  2. subject
  3. explicit entry.label
  4. firstUserMessage (truncated)
  5. auto-populated entry.origin?.label
  6. sessionId prefix fallback

Real behavior proof

Behavior or issue addressed: explicit session labels should survive heartbeat-style transcript summaries, while auto-populated origin labels should not override a real first user message.

Real environment tested: local OpenClaw checkout on this branch after rebasing onto current main.

Exact steps or command run after this patch:

node --import tsx --eval 'import { deriveSessionTitle } from "./src/gateway/session-title.ts";
const cases = {
  explicitLabelBeatsHeartbeat: deriveSessionTitle({ sessionId: "abc123", updatedAt: Date.now(), label: "My App - Dashboard" }, "heartbeat"),
  transcriptBeatsAutoOriginLabel: deriveSessionTitle({ sessionId: "abc123", updatedAt: Date.now(), origin: { label: "Discord: Engineering" } }, "Need help with deployment"),
  originLabelFallbackWithoutTranscript: deriveSessionTitle({ sessionId: "abc123", updatedAt: Date.now(), origin: { label: "Discord: Engineering" } }, null),
  zeroUpdatedAtFallback: deriveSessionTitle({ sessionId: "abcd1234-5678-90ef-ghij-klmnopqrstuv", updatedAt: 0 }),
};
console.log(JSON.stringify(cases, null, 2));'

Evidence after fix:

{
  "explicitLabelBeatsHeartbeat": "My App - Dashboard",
  "transcriptBeatsAutoOriginLabel": "Need help with deployment",
  "originLabelFallbackWithoutTranscript": "Discord: Engineering",
  "zeroUpdatedAtFallback": "abcd1234"
}

Observed result after fix: explicit labels win over heartbeat transcript text; transcript text wins over auto-populated origin labels; origin labels still act as fallback when there is no transcript; zero updatedAt keeps the session-id-only fallback.

What was not tested: full gateway UI rendering was not manually opened; this proof covers the extracted title derivation behavior directly.

Validation

  • pnpm test src/gateway/session-title.test.ts src/gateway/session-utils.test.ts
  • git diff --check upstream/main...HEAD

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: M labels Mar 17, 2026
@greptile-apps

greptile-apps Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts deriveSessionTitle into its own session-title.ts module and extends the precedence chain to include entry.label and entry.origin?.label before falling back to firstUserMessage. This directly addresses the described bug where heartbeat polls (whose first user message was "heartbeat") could overwrite a session's user-assigned label as the displayed title.

Key observations:

  • Behavior change is intentional and correct: adding label/origin.label above firstUserMessage ensures persisted labels are not stomped by transcript-derived content. The SessionEntry type and SessionOrigin type both carry the label?: string field, so the new access paths are fully type-safe.
  • Backward compatibility is preserved: session-utils.ts keeps both the internal import (used at line 961) and a public export { deriveSessionTitle } re-export, so all existing call sites continue to work unchanged.
  • Test consolidation is clean: session-title.test.ts covers all six precedence levels, whitespace trimming, truncation, word-boundary behaviour, and the sessionId fallback. The session-utils.test.ts compatibility test (expect(deriveSessionTitle).toBe(deriveSessionTitleDirect)) correctly validates the re-export via reference equality.
  • Minor: The test case "falls back to sessionId prefix without date when updatedAt missing" passes updatedAt: 0 (not undefined). The test is functionally correct — 0 is treated as falsy — but the name is slightly misleading and was carried over from the old suite.

Confidence Score: 5/5

  • Safe to merge — clean refactor with backward-compatible re-export, correct type usage, and thorough test coverage for the expanded precedence chain.
  • The extraction is mechanical and correct. The new label/origin.label precedence levels are type-safe (both fields exist on SessionEntry/SessionOrigin), intentional (documented in PR description as the bug fix), and well-tested. The only finding is a minor test-description wording issue that does not affect correctness.
  • No files require special attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/session-title.test.ts
Line: 86-96

Comment:
**Misleading test name: "missing" vs. explicitly zero**

The description says `"falls back to sessionId prefix without date when updatedAt missing"`, but `updatedAt` is explicitly set to `0`, not absent (`undefined`). The `SessionEntry` type declares `updatedAt: number` (required), so `undefined` is not a valid value here — the intent is to cover the **falsy-zero** case. Consider renaming for clarity:

```suggestion
  test("falls back to sessionId prefix without date when updatedAt is zero", () => {
    const entry = {
      sessionId: "abcd1234-5678-90ef-ghij-klmnopqrstuv",
      updatedAt: 0,
    } as SessionEntry;
    const result = deriveSessionTitle(entry);
    expect(result).toBe("abcd1234");
  });
```

(This was also the description in the original test copied from `session-utils.test.ts`, so this is a good opportunity to clean it up.)

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

Last reviewed commit: 0605ae3

Comment thread src/gateway/session-title.test.ts Outdated
@JFWaskin
JFWaskin force-pushed the codex/upstream-session-title-tests branch from 0605ae3 to ccbe695 Compare March 23, 2026 04:51

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

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

Comment thread src/gateway/session-title.ts Outdated
@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and the useful part of this older PR is already implemented there.

Close: the central label-preserving derived-title fix is already on current main through #98841, while this older branch’s helper extraction and origin-label fallback are broader stale leftovers rather than necessary remaining work.

Root-cause cluster
Relationship: superseded
Canonical: #98841
Summary: The merged sibling PR is the canonical current-main fix for the session-label derived-title fallback gap, so this older PR is superseded for the central bug.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

So I’m closing this older PR as already covered on main rather than keeping a mostly-duplicated branch open.

Review details

Best possible solution:

Keep the focused current-main label-precedence fix; if maintainers still want helper extraction or origin-label derived-title fallback, open a new targeted PR with a fresh owner decision.

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

No against current main: source inspection shows deriveSessionTitle now returns a normalized label before transcript-derived titles, and the session-list path calls that helper when derived titles are requested.

Is this the best way to solve the issue?

No, this stale branch is no longer the best landing path; the merged focused fix on main solves the central label problem without carrying the older helper extraction and origin-label behavior.

Security review:

Security review cleared: Cleared: the branch changes gateway TypeScript helper/test files only and adds no dependency, workflow, secret, permission, or code-execution surface.

AGENTS.md: found and applied where relevant.

What I checked:

Likely related people:

  • Whoaa512: Authored the session picker MVP commits that introduced heuristic session title derivation and the TUI session picker path. (role: introduced derived-title behavior; confidence: high; commits: 83d5e30027ea, 95f0befd65c4, 40e928a4c4cc; files: src/gateway/session-utils.ts, src/gateway/session-utils.test.ts, src/tui/tui-command-handlers.ts)
  • steipete: Merged the original session picker MVP and authored recent commits in the merged focused session-label fix. (role: merger and recent area contributor; confidence: high; commits: 40e928a4c4cc, 0556f2b8b929, 7c2065603d02; files: src/gateway/session-utils.ts, src/gateway/session-utils.test.ts, src/auto-reply/reply/commands-name.ts)
  • SunnyShu0925: Authored the merged focused PR that added session label precedence to current main. (role: recent fix author; confidence: high; commits: 29d495847ca4, 68e06b9ea148; files: src/gateway/session-utils.ts, src/gateway/session-utils.test.ts)
  • BSG2000: Authored the merged /name chat command that persists user-controlled session labels consumed by this title path. (role: adjacent session-label contributor; confidence: medium; commits: 8326306a5d96, b48238aa88b0; files: src/auto-reply/reply/commands-name.ts, src/auto-reply/reply/commands-name.test.ts)

Codex review notes: model internal, reasoning high; reviewed against f59310a555d7; fix evidence: commit 68e06b9ea148, main fix timestamp 2026-07-03T09:09:31Z.

Copy link
Copy Markdown
Author

Updated the branch against current main and adjusted the title precedence to address the review risk:

  • explicit entry.label now wins before transcript-derived titles
  • auto-populated entry.origin?.label stays behind firstUserMessage, so unlabeled inbound DMs keep transcript-derived summaries instead of regressing to contact/conversation labels
  • deriveSessionTitle remains extracted into session-title.ts with focused tests, while session-utils.ts keeps a compatibility re-export

GitHub Actions are running on the refreshed head.

@JFWaskin

JFWaskin commented May 8, 2026

Copy link
Copy Markdown
Author

Applied the review suggestion by renaming the zero timestamp case to describe the actual updatedAt: 0 input. No behavior change.

I also rebased the branch onto current main and kept the session-title tests isolated in src/gateway/session-title.test.ts with only the compatibility re-export check left in src/gateway/session-utils.test.ts.

Validation:

  • pnpm test src/gateway/session-title.test.ts src/gateway/session-utils.test.ts
  • git diff --check upstream/main...HEAD

@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 8, 2026
@JFWaskin

JFWaskin commented May 8, 2026

Copy link
Copy Markdown
Author

Follow-up status:

  • Real behavior proof now passes after adding the accepted structured proof fields.
  • The remaining current failure is checks-node-auto-reply-reply-dispatch, specifically ACP dispatch tests in src/auto-reply/reply/dispatch-from-config.test.ts.
  • I reproduced that same focused shard failure locally on current upstream/main with pnpm test src/auto-reply/reply/dispatch-from-config.test.ts, so it appears unrelated to this session-title test refactor.

I’m keeping this PR scoped to session-title derivation and will not expand it into the unrelated auto-reply ACP failure unless maintainers request that explicitly.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 8, 2026
@clawsweeper clawsweeper Bot added P2 Normal backlog priority with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels May 17, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 17, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 17, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 23, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 30, 2026
@JFWaskin

JFWaskin commented Jul 1, 2026

Copy link
Copy Markdown
Author

Ready for maintainer review/merge. Status recap against current HEAD 0d90c8d8:

  • Mergeable / clean, CI green, no unresolved review threads.
  • Both bot findings are addressed in code and resolved:
    • Greptile P2 (test name) — the zero-timestamp test now reads "omits date from sessionId fallback when updatedAt is zero".
    • Codex P2 (precedence) — firstUserMessage sits ahead of auto-populated origin.label, so unlabeled inbound DMs still get transcript-derived titles. Only explicit entry.label outranks the first user message.

Precedence at HEAD: displayName → subject → explicit label → firstUserMessage → origin.label → sessionId prefix.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 0d90c8d8ce

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

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 1, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. P2 Normal backlog priority with limited blast radius. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 4, 2026
@clawsweeper

clawsweeper Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants