Conversation
…y.jsonl display Two bugs fixed: 1. processSessionFile: history.jsonl display was checked before JSONL extractSessionAiTitleFromEnd, so AI-generated titles (ai-title) and /rename titles (custom-title) were never used for CLI-started sessions. Swapped so JSONL is consulted first, with history.jsonl as fallback. 2. extractSessionAiTitleFromEnd: reverse-scan stopped at the first match, which was always last-prompt (at the end of the file), shadowing both ai-title and custom-title that appeared earlier. Changed to a full forward scan that collects all three types and returns them in priority order: custom-title > ai-title > last-prompt. Final priority chain: custom_name (DB) > custom-title (JSONL) > ai-title (JSONL) > last-prompt (JSONL) > history.jsonl display > Untitled Claude Session Added 12 unit tests covering all title sources and edge cases.
📝 WalkthroughWalkthroughClaude session synchronization now prioritizes matching JSONL title events, then falls back to history and default names. New isolated tests cover lookup maps, title precedence, database overrides, and ignored transcript inputs. ChangesClaude title synchronization
Sequence Diagram(s)sequenceDiagram
participant SessionFile
participant ClaudeSessionSynchronizer
participant History
ClaudeSessionSynchronizer->>SessionFile: scan matching JSONL title events
SessionFile-->>ClaudeSessionSynchronizer: title candidates
ClaudeSessionSynchronizer->>History: read fallback nameMap when needed
History-->>ClaudeSessionSynchronizer: display name
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
server/modules/providers/list/claude/claude-session-synchronizer.provider.ts (2)
175-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRename
extractSessionAiTitleFromEndto reflect its new behavior.The function now scans forward (not from the end) and collects all three title types (
custom-title,ai-title,last-prompt), not just AI titles. The nameextractSessionAiTitleFromEndis misleading to future maintainers. Consider something likeextractSessionTitleorresolveSessionTitleFromTranscript.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts` around lines 175 - 178, Rename extractSessionAiTitleFromEnd to a name reflecting forward transcript scanning and resolution of custom-title, ai-title, and last-prompt values, such as extractSessionTitle or resolveSessionTitleFromTranscript; update its declaration and every call site consistently.
148-151: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueFile is read twice in
processSessionFile.
extractFirstValidJsonlData(line 117) streams the file for sessionId/cwd, thenextractSessionAiTitleFromEnd(line 148) loads the entire file again viareadFile. For large session transcripts this doubles I/O and loads the full content into memory. Consider merging both passes into a single scan, or at minimum using a streaming approach inextractSessionAiTitleFromEndto avoid loading the entire file.Also applies to: 180-180
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts` around lines 148 - 151, Refactor processSessionFile and extractSessionAiTitleFromEnd to avoid reading the session file twice and loading it fully into memory: combine session metadata and AI-title extraction into one streaming scan, or make extractSessionAiTitleFromEnd stream the file incrementally. Preserve the existing fallback to nameMap when no title is found.server/modules/providers/tests/claude-sessions.test.ts (2)
145-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a direct test for
custom-titlevsai-titlepriority.The current tests verify
custom-titlebeatslast-promptandai-titlebeatslast-prompt, but no test has bothcustom-titleandai-titlepresent to directly verifycustom-title > ai-title. Adding this would close the last gap in the priority chain coverage.Also applies to: 190-227
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/tests/claude-sessions.test.ts` around lines 145 - 188, The tests lack direct coverage for priority between custom-title and ai-title. Add a test alongside the existing synchronizeFile priority tests that writes both custom-title and ai-title records to the same session JSONL, runs ClaudeSessionSynchronizer.synchronizeFile, and asserts the stored session custom_name equals the custom-title value, verifying custom-title takes precedence over ai-title.
271-307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for
last-promptas the sole title source.The priority chain includes
last-promptbeforehistory.jsonl display, but no test verifies this specific case. Iflast-prompthandling is accidentally removed, no existing test would catch it. A test where the JSONL has only alast-promptevent (noai-titleorcustom-title) andhistory.jsonlhas a competing display name would close this gap.🧪 Suggested test
test('synchronizeFile uses last-prompt from JSONL when no custom-title or ai-title exists', { concurrency: false }, async () => { const tmp = await mkdtemp(path.join(os.tmpdir(), 'claude-sync-lastprompt-')); const workspacePath = path.join(tmp, 'workspace'); await mkdir(workspacePath, { recursive: true }); const restoreHomeDir = patchHomeDir(tmp); try { const claudeHome = path.join(tmp, '.claude'); await mkdir(claudeHome, { recursive: true }); await writeFile( path.join(claudeHome, 'history.jsonl'), JSON.stringify({ sessionId: 'test-session-1', display: 'history-display' }) + '\n', 'utf8', ); await writeSessionJsonl(workspacePath, 'test-session-1.jsonl', [ JSON.stringify({ type: 'last-prompt', lastPrompt: 'My last prompt', sessionId: 'test-session-1' }), ]); await withIsolatedDatabase(async () => { const synchronizer = new ClaudeSessionSynchronizer(); const result = await synchronizer.synchronizeFile( path.join(workspacePath, 'test-session-1.jsonl'), ); assert.ok(result); const session = sessionsDb.getSessionById(result!); assert.equal(session?.custom_name, 'My last prompt'); }); } finally { restoreHomeDir(); await rm(tmp, { recursive: true, force: true }); } });Also applies to: 229-269
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/tests/claude-sessions.test.ts` around lines 271 - 307, Add a regression test alongside the existing synchronizeFile title-selection tests covering a JSONL file containing only a last-prompt event, with no ai-title or custom-title, while history.jsonl contains a competing display name. Use ClaudeSessionSynchronizer.synchronizeFile and assert the persisted session custom_name equals the last-prompt value, verifying that last-prompt takes priority over history display.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`:
- Around line 175-178: Rename extractSessionAiTitleFromEnd to a name reflecting
forward transcript scanning and resolution of custom-title, ai-title, and
last-prompt values, such as extractSessionTitle or
resolveSessionTitleFromTranscript; update its declaration and every call site
consistently.
- Around line 148-151: Refactor processSessionFile and
extractSessionAiTitleFromEnd to avoid reading the session file twice and loading
it fully into memory: combine session metadata and AI-title extraction into one
streaming scan, or make extractSessionAiTitleFromEnd stream the file
incrementally. Preserve the existing fallback to nameMap when no title is found.
In `@server/modules/providers/tests/claude-sessions.test.ts`:
- Around line 145-188: The tests lack direct coverage for priority between
custom-title and ai-title. Add a test alongside the existing synchronizeFile
priority tests that writes both custom-title and ai-title records to the same
session JSONL, runs ClaudeSessionSynchronizer.synchronizeFile, and asserts the
stored session custom_name equals the custom-title value, verifying custom-title
takes precedence over ai-title.
- Around line 271-307: Add a regression test alongside the existing
synchronizeFile title-selection tests covering a JSONL file containing only a
last-prompt event, with no ai-title or custom-title, while history.jsonl
contains a competing display name. Use ClaudeSessionSynchronizer.synchronizeFile
and assert the persisted session custom_name equals the last-prompt value,
verifying that last-prompt takes priority over history display.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e306b427-6796-4857-b9dc-c6e104c09fbf
📒 Files selected for processing (2)
server/modules/providers/list/claude/claude-session-synchronizer.provider.tsserver/modules/providers/tests/claude-sessions.test.ts
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
server/modules/providers/list/claude/claude-session-synchronizer.provider.ts (1)
175-178: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMethod name
extractSessionAiTitleFromEndis now misleading.The implementation scans forward (line 187:
for (let index = 0; index < lines.length; ...)), but the name still says "FromEnd." Consider renaming toextractSessionTitleorextractSessionTitleFromTranscriptto match the forward-scan behavior and avoid confusing future maintainers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts` around lines 175 - 178, Rename the misleading `extractSessionAiTitleFromEnd` method to `extractSessionTitle` (or `extractSessionTitleFromTranscript`) to reflect its forward transcript scan, and update every call site and related references consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/modules/providers/tests/claude-sessions.test.ts`:
- Around line 141-143: Add a concurrency-disabled test near the existing
synchronizeFile title tests that creates a session JSONL containing only a
last-prompt event, optionally with a conflicting history.jsonl display, then
asserts synchronizeFile stores the lastPrompt value as custom_name. Also add or
extend a test containing custom-title, ai-title, and last-prompt events to
verify the complete custom-title > ai-title > last-prompt precedence chain,
using the existing test helpers and cleanup patterns.
---
Nitpick comments:
In
`@server/modules/providers/list/claude/claude-session-synchronizer.provider.ts`:
- Around line 175-178: Rename the misleading `extractSessionAiTitleFromEnd`
method to `extractSessionTitle` (or `extractSessionTitleFromTranscript`) to
reflect its forward transcript scan, and update every call site and related
references consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 1431b534-85b3-4339-8636-3b47e8419ab7
📒 Files selected for processing (2)
server/modules/providers/list/claude/claude-session-synchronizer.provider.tsserver/modules/providers/tests/claude-sessions.test.ts
| // --------------------------------------------------------------------------- | ||
| // extractSessionAiTitleFromEnd — tested via synchronizeFile | ||
| // --------------------------------------------------------------------------- |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Missing test for last-prompt as the sole JSONL title source.
The last-prompt fallback within extractSessionAiTitleFromEnd is never exercised in isolation. In every test where last-prompt appears (lines 171, 210, 331, 373), a higher-priority event (ai-title or custom-title) is always present, so last-prompt never wins. There's also no test with all three event types present to verify the full custom-title > ai-title > last-prompt priority chain.
Consider adding a test where last-prompt is the only title event (optionally with a competing history.jsonl display) to verify it takes priority over history and produces the expected custom_name.
🧪 Suggested test for last-prompt fallback
test('synchronizeFile uses last-prompt from JSONL when no custom-title or ai-title exists', { concurrency: false }, async () => {
const tmp = await mkdtemp(path.join(os.tmpdir(), 'claude-sync-lastprompt-'));
const workspacePath = path.join(tmp, 'workspace');
await mkdir(workspacePath, { recursive: true });
const restoreHomeDir = patchHomeDir(tmp);
try {
const claudeHome = path.join(tmp, '.claude');
await mkdir(claudeHome, { recursive: true });
// Include a competing history.jsonl display to verify last-prompt wins over it.
await writeFile(
path.join(claudeHome, 'history.jsonl'),
JSON.stringify({ sessionId: 'test-session-1', display: 'history-display' }) + '\n',
'utf8',
);
await writeSessionJsonl(workspacePath, 'test-session-1.jsonl', [
JSON.stringify({ type: 'last-prompt', lastPrompt: 'the final user prompt', sessionId: 'test-session-1' }),
]);
await withIsolatedDatabase(async () => {
const synchronizer = new ClaudeSessionSynchronizer();
const result = await synchronizer.synchronizeFile(
path.join(workspacePath, 'test-session-1.jsonl'),
);
assert.ok(result);
const session = sessionsDb.getSessionById(result!);
assert.equal(session?.custom_name, 'the final user prompt');
});
} finally {
restoreHomeDir();
await rm(tmp, { recursive: true, force: true });
}
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/modules/providers/tests/claude-sessions.test.ts` around lines 141 -
143, Add a concurrency-disabled test near the existing synchronizeFile title
tests that creates a session JSONL containing only a last-prompt event,
optionally with a conflicting history.jsonl display, then asserts
synchronizeFile stores the lastPrompt value as custom_name. Also add or extend a
test containing custom-title, ai-title, and last-prompt events to verify the
complete custom-title > ai-title > last-prompt precedence chain, using the
existing test helpers and cleanup patterns.
|
I hit this bug independently and worked out the same cause before finding this PR, so here is a confirmation plus an offer of help. Still reproducing. On the published There is a third symptom worth recording. Claude Code writes On the conflict. Only After the rebase: 22/22 in that file, 407/409 in the full server suite, typecheck clean, no new lint warnings. The single failure is @swu45, are you still able to land this? If you would rather not, I am happy to open the rebased branch directly against Two related things for whoever picks this up: #1178 rewrites the same function to bound the scan for performance, so these two will need sequencing; and #747 covers the |
The function scans forward and returns a custom-title, ai-title or last-prompt, so its name described neither its direction nor its result. Addresses the review comment on siteboon#982.
|
Following up on my comment above: I have opened the rebased branch directly against @swu45's two commits are unchanged there and keep their authorship. swu45#1 is still open against this branch, so if @swu45 would rather land it here, merging that makes this PR mergeable again and #1258 can be closed. Either route is fine by me. |
Problem
CloudCLI sidebar shows the user's first typed message as the session title, instead of the AI-generated title that Claude Code produces. For example:
How to set up a React project with Tailwind CSS?(user's raw prompt)/resumeReact + Tailwind Project Setup Guide(AI-generated title)Root cause — two bugs
Bug 1:
processSessionFilecheckshistory.jsonlbefore JSONLhistory.jsonl'sdisplayfield is a snapshot of user input—it was never a title.buildLookupMapuses first-write-wins, sonameMap.get()always returns the user's first message for CLI-started sessions. Since this check runs beforeextractSessionAiTitleFromEnd, the JSONLai-titleandcustom-titleevents are never consulted.Bug 2:
extractSessionAiTitleFromEndreverse-scan stops atlast-promptThe reverse-scan loop returns on the first match. Because
last-promptalways appears at the end of the JSONL file, it is always the first match, shadowingai-titleandcustom-titlethat appear earlier.Fix
1. Swap JSONL and history.jsonl priority in
processSessionFileJSONL title extraction (
extractSessionAiTitleFromEnd) now runs before thehistory.jsonllookup, so AI-generated and/renametitles take precedence.2. Full scan in
extractSessionAiTitleFromEndwith proper priorityChanged from a reverse-scan-early-return to a full forward scan that collects all three title types separately, then returns them with the correct priority:
Final title resolution chain
Files changed
server/modules/providers/list/claude/claude-session-synchronizer.provider.tsserver/modules/providers/tests/claude-sessions.test.ts.gitignoreissue/for local trackingRelated
custom-titleentries from session JSONL so Claude Code/renameis reflected in the sidebar #747 — addedcustom-titlesupport toextractSessionAiTitleFromEnd(didn't fix the priority bug blocking it)Summary by CodeRabbit