[BUG] Session titles revert to last-prompt text after session close, overriding custom-title entries
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
These chat sessions that worked with the 'first line as title', were critical to me correlating work in Claude Code chat sessions <-> kanbn task manager used for the project.
So now I'm having to have Claude try to rename the things that it is automatically renaming, that I already had a solid title for in the very first line.
Chat sessions get renamed seemingly at random, and are then mostly un-searchable as the 'session title' has been auto-edited to some arbitrarily-chosen text 'somewhere in the chat session' by (Claude Code, VScode ???).
After a session ends, the Claude Code VS Code extension writes a last-prompt entry to the session's JSONL file. This entry becomes the final entry in the file, and the sidebar then displays the lastPrompt text (the user's final message) as the session title — overriding any custom-title entry that was written earlier in the file.
Previously, the sidebar used the first user message as the session title by default. Now, closed sessions display their last message text instead. This makes sessions unrecognisable in the sidebar, especially for users who rely on writing a descriptive title as the first line of their first message (e.g. "Task: Task 186.1 - LLM Scoring Heatmap Load Performance — Backend Query Optimisation and Caching" to identify sessions and pick up e.g. kanbn Task cards to continue the work.
What Should Happen?
The session sidebar should display the custom-title entry as the title regardless of whether a last-prompt entry was appended afterward.
Previously, sessions were titled by their first user message line, and the title persisted correctly across VS Code restarts and session close/reopen cycles. The last-prompt entry should not override a custom-title entry when displaying session names in the sidebar.
Manual renames (via the edit icon in the sidebar) do work correctly because VS Code appends the new custom-title after last-prompt, making it the final entry. This confirms the sidebar is using "last entry wins" logic rather than "custom-title wins" logic. The fix would be to prioritise any custom-title entry over last-prompt for display purposes.
But again, this adds a lot of friction, when 'first line is title' worked fine, with no more overhead than pasting in the task card title that I was working on.
Error Messages/Logs
No error messages. The behaviour is silent — sessions simply display the wrong title (last message text instead of the session's custom-title).
Example JSONL file tail showing the problem:
...conversation entries...
{"type":"custom-title","sessionId":"25e59705-...","customTitle":"Find the chat session where we added a custom hook or script..."}
{"type":"last-prompt","lastPrompt":"Good, will test by restarting / reloading window shortly...","sessionId":"25e59705-..."}
Sidebar displays: "Good, will test by restarting / reloading window shortly..."
Expected display: "Find the chat session where we added a custom hook or script..."
Steps to Reproduce
- Open Claude Code in VS Code and start a new session.
- Type a descriptive first message (e.g. "Task: Task 149 - My Task Name").
- Have a multi-turn conversation.
- Close the session (close the tab or open a new session, or reopen VScode).
- Observe the closed session in the sidebar — it now displays the text of your last message, not your first message or any previously set custom-title.
- Restart VS Code — the wrong title persists across restarts.
Root cause (confirmed by inspection of JSONL files): the extension appends a {"type":"last-prompt","lastPrompt":"..."} entry when a session closes. The sidebar then displays lastPrompt because it is the final entry in the file, taking precedence over earlier custom-title entries.
Manual renames via the sidebar edit icon do persist correctly, because VS Code appends the new custom-title after last-prompt — confirming the issue is position-dependent, not a reading failure.
Claude Model
Sonnet (default)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Unknown — noticed after a recent extension update. Last known working behaviour: sessions displayed the first line of the first user message as the title by default, and this persisted correctly after session close and VS Code restart.
Claude Code Version
2.1.62 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
VS Code version: 1.109.5 (x64)
The workaround we implemented locally:
- A systemd user timer runs
find-session.py --fix-last-promptevery 2 minutes - It detects sessions where
last-promptis the final JSONL entry and nocustom-titlefollows it - Appends a compact
custom-title(from the session's first user message line) afterlast-prompt - Restores the original file mtime afterward to preserve sidebar sort order
This workaround works, but it means sessions show the wrong title for up to 2 minutes after closing, and requires external tooling to compensate for what should be (or was, now isn't, but could be again...) built-in behaviour.
Impact: users who rely on descriptive first-line titles to identify sessions (a natural workflow given the sidebar shows session titles) will find all their closed sessions display meaningless last-message text instead. With 500+ sessions, this makes the session list unusable without the workaround.
System Details Report
---
Report details
- Date generated: 2026-03-08 14:52:19
Hardware Information:
- Hardware Model: Gigabyte Technology Co., Ltd. X870E AORUS ELITE WIFI7
- Memory: 128.0 GiB
- Processor: AMD Ryzen™ 9 7900X × 24
- Graphics: AMD Radeon™ Pro WX 3200 Series
- Graphics 1: AMD Radeon™ Pro WX 3200 Series
- Disk Capacity: 13.0 TB
Software Information:
- Firmware Version: FA6d
- OS Name: Ubuntu 24.04.4 LTS
- OS Build: (null)
- OS Type: 64-bit
- GNOME Version: 46
- Windowing System: Wayland
- Kernel Version: Linux 6.17.0-14-generic
11 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Not a duplicate of - https://github.com/anthropics/claude-code/issues/31394 -
Possible partial overlap with 31394, but that is on MacOS, and also does not address the issue in mine of 'user does not have control over chat session names' in general.
Not a duplicate of https://github.com/anthropics/claude-code/issues/31270 -
that issue's 'The names revert to the truncated first prompt' - is exactly the reverse of what I have, in some way, I think?
Possibly a partial duplicate of https://github.com/anthropics/claude-code/issues/25090 -
But, that issue is related to Windows, and also does not address the issue in mine of 'user does not have control over chat session names' in general.
I had Claude Code kick off some research in NotebookLM and document what it could trace through the 'duplicate trail'. I'm not sure if this is helpful, but as ever, the AI sounds confident about it ;-)
Notebook - https://notebooklm.google.com/notebook/98a2abca-848f-4838-b32b-e1586eadca57
Mindmap -
<img width="3840" height="5737" alt="Image" src="https://github.com/user-attachments/assets/f45b3d44-75cd-4c75-8307-ffdee1f93773" />
Technical Briefing: Session Renaming and Persistence Failures (GitHub Issues 23151–32150)
To minimize notification noise and consolidate engineering triage, all internal and community tracking must be directed to the following designated issues:
Recommendation: All users should upvote and watch Issue #31394 to provide the team with consistent telemetry for current-version performance and to receive status updates.
--------------------------------------------------------------------------------
The persistent failure of the /rename command and the subsequent loss of session titles represent a critical State Synchronization Failure. This originated during the Architectural Pivot where the system transitioned from the centralized sessions-index.json to a decentralized JSONL-stream scanning model.
Technical synthesis identifies a three-pronged failure in the current architecture:
--------------------------------------------------------------------------------
The following matrix tracks the fragmented history of this regression across environments and versions.
Issue # Status Environment Primary Symptom Linked Issue(s)
#27202 Open (Stale) Linux (Fedora) Scanner ignores sessionId; titles route to wrong files. #24065, #27195
#31394 Open macOS (Darwin 25.3.0) Rename fails to persist in /resume list (v2.1.70). -
#26249 Duplicate macOS (Sequoia) Cannot resume by name; displayName missing from index. #26134
#23151 Duplicate macOS Renamed sessions invisible in picker (missing summary). #23061
#26134 Duplicate Windows 11 /rename visual success but 0 persistence in index. #26249
#24729 Open Windows 11 Indexing stops; summaries empty (since Feb 2, 2026). #23151
#25090 Open Windows 11 Name persists for one resume, vanishes after second exit. #24065
#31270 Open VS Code (Win) Custom names reset to firstPrompt after window reload. #32150
#29922 Open Linux Regression: Name lost after context compaction triggers. #26121
#32150 Open VS Code (Linux) Sidebar titles revert to last-prompt text on close. #31270
#25905 Duplicate macOS CLI resume fails; index entry absent after rename. #26249
#24065 Open (Stale) macOS Custom title not saved to index; resume list ignores it. #27202
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
As of v2.1.70, the /rename feature is Effectively Cosmetic and Unreliable. Titles may appear to save during a session but are frequently lost due to compaction, auto-titling on exit, or IDE reloads.
Known Manual Workarounds:
Closing Directive: All users on the latest version (v2.1.70) must move engagement to Issue #31394. This issue is the primary focus for engineers investigating the current Darwin 25.3.0 environment failures.
Claude Code has at least for me, on my linux box, figured it out.
My chat sessions titles are now stable.
Root Cause Breakdown
Layer 1: Index Schema Missing
customTitlesessions-index.jsonschema fields:sessionId,fullPath,fileMtime,firstPrompt,summary,messageCount,created,modified,gitBranch,projectPath,isSidechain. NocustomTitleordisplayNamefield. The/renamecommand writes to JSONL only; the picker reads from the index only.Layer 2: VS Code "Last Entry Wins"
The VS Code extension displays session titles using priority: last entry in JSONL file wins. On session close, it appends
{"type":"last-prompt","lastPrompt":"..."}. Since this appears aftercustom-title, the last message text becomes the displayed title.Layer 3: Auto-Titling Overwrites
Context compaction and auto-titling routines don't check for existing user-defined titles. They regenerate summaries that stomp custom names.
Layer 4: Scanner Ignores
sessionIdSince v2.1.49, the JSONL scanner applies
custom-titleto the file it's found in, not the session specified in thesessionIdfield. Renaming after/resumetitles the wrong session entirely.Version Timeline
| Version | Status | Notes |
|---------|--------|-------|
| v2.1.29 | Broken | #23151: customTitle not synced to summary |
| v2.1.31 | Broken | #24729: session index stops updating entirely |
| v2.1.37–42 | Broken | #24065, #26134, #26249: /rename not indexed |
| v2.1.47 | Partial fix | #23610 fixed, but rename still not indexed |
| v2.1.49 | New bug | JSONL scanner ignores sessionId → wrong session titled |
| v2.1.53 | Fixed briefly | Custom names appeared in Ctrl+E picker |
| v2.1.55 | Regressed | Fix reverted (possibly side-effect of EINVAL fix) |
| v2.1.62–69 | Broken | VS Code: last-prompt overrides custom-title |
| v2.1.70 | Broken | Current — all symptoms persist |
Our Local Workarounds
auto-title-session.shhook — UserPromptSubmit hook that writes first-line-as-title to JSONLfind-session.py --retitle— Retroactive batch retitling of untitled sessionsfind-session.py --fix-last-prompt— Detects sessions wherelast-promptis final entry, appendscustom-titleafter it (runs on systemd timer every 2 min)Primary Sources
| # | Issue | State | Platform | Key Contribution |
|---|-------|-------|----------|-----------------|
| 1 | #32150 | OPEN | Linux/VS Code | last-prompt overrides custom-title (yours) |
| 2 | #25090 | OPEN | Windows/Cross | Most active tracker, version-by-version log |
| 3 | #24729 | OPEN | Windows | Infrastructure: session index broken since v2.1.31 |
| 4 | #31270 | OPEN | Windows/VS Code | sessions-index.json missing customTitle field |
| 5 | #29922 | OPEN | Linux | Name lost after compaction (regression label) |
| 6 | #27202 | OPEN | Cross | Scanner ignores sessionId — titles wrong session |
| 7 | #24065 | OPEN | macOS | Early canonical report, frequently referenced |
| 8 | #31394 | OPEN | macOS | Latest report, v2.1.70 |
| 9 | #26134 | OPEN | Windows | Duplicate label but has useful confirmations |
| 10 | #23151 | CLOSED | macOS | First to identify customTitle vs summary gap |
| 11 | #26249 | CLOSED | Cross | Root cause analysis: rename not indexed |
| 12 | #25905 | CLOSED | macOS | Rename makes sessions unresumable |
NotebookLM Analysis
NotebookLM identified four architectural failure layers: cache/index desync, auto-titler overwrite, last-entry-wins UI logic, and scanner sessionId ignore. The brief fix in v2.1.53 (regressed in v2.1.55) proves the team knows where the fix is — it just didn't stick, likely reverted as a side-effect of the Windows EINVAL fix in the same release.
The suggested fix is straightforward: add
customTitleto thesessions-index.jsonschema, have/renamewrite to both JSONL and index, and have the UI prioritisecustom-titleentries overlast-promptregardless of position in the file.Open Questions
sessions-index.jsonschema be updated, or will the architecture shift to JSONL-only scanning?Update: additional failure modes discovered
Since filing this issue I've done a deeper investigation and found two more failure modes beyond the
last-promptoverwrite described above. All three share the same root cause: the extension's title display relies on raw string scanning of the last 64KB of each JSONL file.Failure Mode 2: 64KB tail window eviction
On long sessions (>64KB of content after the
custom-titleentry), the title entry gets pushed outside the scanner's window entirely. The extension falls back tolastPromptor shows nothing. This is the same issue described in #33165.Failure Mode 3: Cross-session content contamination
The scanner searches for
"customTitle": "as a raw string — it does NOT verify it's reading a top-level JSONL entry. If a tool result or conversation message contains the literal string"customTitle": "some text"(e.g. when debugging session management), the extension picks up that text as the session's title. This causes one session's title to appear on a completely different session.Updated workaround
The systemd timer fix described in the original report has been expanded:
UserPromptSubmithook now re-appends the correctcustom-titleon every prompt (not just the first), sourced from a persistenttitle-registry.json. This defeats both the 64KB eviction and the content contamination for active sessions.CUSTITSin the sidebar rename — the timer detects this, strips the prefix, and records the title as permanent (never overwritten by automation).--fix-task-titlesmode backfills the registry from first user messages and re-asserts correct titles on inactive sessions every 2 minutes.All three failure modes would be eliminated if titles were stored in a separate index file rather than scanned from conversation content.
Related issues
I've collected all the issues that stem from this same root cause — see the cross-post on #33165 for the full list.
Anthropic fix this please. work item to chat session correlation is impossible with this bug
Update: Root cause traced to extension source —
vj()two-pass scannerI've traced this through the minified
extension.js(confirmed againstanthropic.claude-code-2.1.71-linux-x64). The display title isn't "last entry wins" as I initially described — it's more subtle.The extension resolves titles via:
Where
vj()scans with two sequential patterns — compact first, then spaced:The spaced pass runs after the compact pass and overwrites
resulton every match. So the last spaced-format occurrence always wins — even if it appears early in the file. The extension writes its owncustom-titleentry in spaced format at session start, so it permanently dominates any compact-format entries appended later. This explains why/renameappears to work initially but fails on reload [#25090] [#31394] — the appended entry uses compact format and loses to the earlier spaced entry on rescan.Additionally, the extension only reads the last 64KB of each JSONL file (
Z1 = 65536constant). For long sessions, the originalcustom-titleentry near byte 0 falls outside the tail window entirely, andvj()falls through tolastPrompt. This is the same mechanism reported in [#33165], which proposes storing titles in a separate index rather than relying on tail-scanning large JSONL files.Three failure modes, one root cause:
| # | Failure | Mechanism | Also reported in |
|---|---------|-----------|-----------------|
| 1 |
last-promptoverwrites title | Extension appendslast-promptafter session close; no logic to prefercustomTitle| [#25090], [#31394] || 2 | 64KB tail window eviction | Long sessions push
custom-titleoutside the scanner's 64KB window | [#33165] || 3 | Cross-session content contamination |
vj()does raw string search — a"customTitle": "..."string inside a tool result or conversation message gets picked up as the title | — |Related: [#27202] found that
/renameafter/resumewrites thecustom-titleentry into the wrong JSONL file entirely (the containing session, not the targetsessionId), because the scanner ignores thesessionIdfield. [#31270] reports thatsessions-index.jsonhas nocustomTitlefield, so custom names are lost on reload even when correctly persisted in JSONL.Workaround (working on Linux): I have a Python script + systemd timer (every 2 min) that:
vj()scan including the 64KB tail window to detect mismatched titlescustom-titleentries in spaced JSON format (so they win the two-pass scan)Effectively within ~2 minutes of a session closing, the title is corrected — the timer fires every 2 min, and skips sessions modified in the last 5 min. So a closed session gets its title fixed on the next timer tick after the 5-minute cooldown. Worst case ~7 minutes, typical case ~2–5 minutes. Happy to share the full script if useful.
Suggested fix in the extension: Replace the
vj()scan-order approach with semantic priority: if anycustomTitleentry exists in the tail window, use it regardless of format.lastPromptshould only be a fallback when nocustomTitleexists at all. Longer term, persistcustomTitleinsessions-index.json[as suggested in #31270 and #33165] so it survives both the 64KB window and reload cycles.Reinforcing this report with a mechanism explanation from reading the extension source (2.1.112):
The session-list metadata parser (
lp()and the inline variant insidefetchSessions) only scans the last 64KB of the JSONL forcustomTitle/aiTitle/lastPrompt/summary. The resolver chain is:So
customTitledoes sit ahead oflastPromptin the priority chain, but only if it's found. When a session grows past ~64KB after thecustom-titleentry, the entry falls out of the tail window. The parser then can't find it, falls through, andlastPrompt(which keeps getting re-written at the tail on every prompt) wins.This explains both the position-dependent "last entry wins" behaviour observed here and why manual renames via the sidebar edit icon work (they write a fresh
custom-titleat the tail, back inside the window).Observed across 19 sibling sessions in one project in #49996 (which I'm closing as a duplicate of this). Suggested fix: either index
custom-titleentries via a full-file scan (not tail-only), or movefirstPrompt(head)ahead oflastPromptin the priority chain so the default fallback is stable.Two follow-ups from applying the reorder fix locally on 2.1.120:
Empirical confirmation. Patched both resolver sites to put
firstPrompt(head)ahead oflastPrompt(i.e.customTitle || aiTitle || firstPrompt(head) || summary || lastPrompt). After window reload, the sidebar immediately surfaced existing forks with their identity titles instead of all rendering whatever recent message happened to be in each one's tail-64KB. Forks I'd had open for weeks but couldn't visually distinguish became individually recognizable: same conversations, just stable titles. No other behavior changed; resume picker still has access tolastPromptfor "where did I leave off" via direct field access, and the chain is just no longer hijacking the title slot.Two resolver sites, not one. In
extension.jsthere are two near-identical title-resolver chains, both with the same wrong ordering:let L = H || X5(G,"lastPrompt") || X5(G,"summary") || D;whereHiscustomTitle/aiTitleandDis the precomputed firstPrompt result.async fetchSessions()(the sidebar-list rebuild). Chain readslet L = (...customTitle/aiTitle...) || x9(q,"lastPrompt") || x9(q,"summary") || Ca(N);whereCa(N)is the firstPrompt extractor on the head buffer.Different extractor function names, otherwise the same shape. Whoever takes this upstream should patch both; fixing only one leaves the title flicker between sidebar refresh and file-watcher recompute. The duplication itself is suspicious; an extracted shared helper would prevent this from re-diverging later.
Variable names will change between releases; locate by structure (
<...customTitle/aiTitle...> || <X>(...,"lastPrompt") || <X>(...,"summary") || <firstPromptFn>).---
🤖 Reply written by Claude (claude-opus-4-7), posted by @ojura. The local fix lives in a personal
patch-antigravityskill that re-applies a small set of surgical edits whenever the bundled extension updates. The reason this comment can name both resolver sites concretely instead of guessing: a public TS source dump matches this build symbol-for-symbol, which made it trivial to find the two near-identical chains and confirm the architecture before swapping the order. Working from readable source turns "I think this is the chain" into "here are the two specific sites that need to be patched together": the difference between a vague report and a one-PR fix.Same symptom on Claude Desktop / macOS, but possibly via a different code path than the VSCode
last-promptoverwrite described above. Sharing as a second test case in case it helps localize the bug.Repro on macOS Desktop
Expected: Desktop side panel shows
TEST TITLE.Actual:
General coding sessionUntitledWhat's different from the VSCode case
In my repro the JSONL has only the customTitle record as its first line plus the headless
/review-mrexchange. There is nolast-promptrecord anywhere in the file, so Desktop isn't being misled by last-entry-wins; it's never reading the customTitle in the first place.That suggests Desktop's session-import / title-resolution path doesn't consult
type: "custom-title"records at all on macOS, separate from whatever's overriding them on VSCode after-the-fact.Evidence Desktop knows about the record type
From
strings /Applications/Claude.app/Contents/Resources/app.asar:"custom-title"(record-type literal)customTitle:A.trim()andcustomTitle:t.trim()(write-side handlers, presumably the right-click → rename path)So Desktop has write-side handlers for customTitle but apparently no read-side resolver on import.
Why this matters
I run an automated review on every new GitLab MR for my team's repo via
claude --print --session-id <uuid> --name "SPT MR REVIEW: !<iid>" "/review-mr <iid>". Every one of those sessions shows up in Desktop's side panel as "General coding session" instead ofSPT MR REVIEW: !84. After a few days the side panel is unsearchable. Right-click rename works but is per-session manual toil.Versions
Happy to test patches or provide more context if useful.
Still present on macOS CLI v2.1.220 (so it isn't only the VS Code extension), and there's a second-order effect that makes it considerably worse than "the name is ugly".
The prompt-derived name doesn't just get displayed — it gets persisted as a real
custom-titlerecord in the transcript. In my~/.claude/projects/**I have several titles that are verbatim 75–80-character truncations of a first prompt, stored as ordinary title records, e.g.:And since 2.1.204 a newly created session copies the previous session's title record into line 1 of its own file (see #78264, #80906), those truncated prompt strings then propagate to every child session. One such 79-character fragment ended up as the name of 5 unrelated sessions here; another spread to 2. Because a session that already has a title never gets an
ai-titlegenerated, the fragment is permanent in all of them.So the two bugs compound: this one manufactures a garbage title, and the copy-on-new bug then replicates it across the picker.