[BUG] Opening a chat re-appends a duplicate mode/custom-title record to its JSONL, bumping mtime and reordering it to the top of Recent chats
What's Wrong?
Claude Desktop appends bookkeeping records (mode, custom-title, and in other cases ai-title) to a session's JSONL transcript in ~/.claude/projects/<project>/ unconditionally — even when the value is byte-identical to a record already in the file — every time you open the chat, and again (across multiple sessions at once) on app quit. Each append rewrites the file and bumps its filesystem mtime.
The Claude Code VS Code extension's "Recent chats" picker sorts by file mtime. So a chat whose newest actual message is months old jumps to the top of Recent chats as "modified recently" the instant you open it — wedged confusingly between genuinely fresh chats and older ones — even though no conversation happened. Over time the redundant records pile up (one of my files now holds 22 identical mode records and 29 identical custom-title records), bloating the transcript with no information gain.
This is also relevant to the session-retention/cleanup bug (#59248, #41458): cleanupOldSessionFiles keys deletion off stat.mtime, and this is Claude's own code mutating mtime in ways unrelated to user activity.
How this differs from #40787 / #62201 (so it isn't auto-closed as a duplicate)
Those report title reversion and sticking — a manually-set title getting overwritten by a stale/auto title (alternating, different values; a renameSession race), or a worktree session propagating its title to siblings. This report is not about reversion or worktree: there's no manual rename, the appended records are byte-identical to existing ones (mode:"normal", same custom-title), it includes the mode record (not just custom-title), and the harm being reported is the mtime bump reordering Recent chats (and feeding the mtime-keyed retention deletion), which those issues never mention. The shared root — "write a record without checking whether it duplicates the current value" — is the same defect class, but the trigger, the records, and the user-visible damage are distinct.
What Should Happen?
Two independent fixes, either of which resolves the user-visible symptom:
- Don't append a bookkeeping record (
mode,custom-title,ai-title) when its value is identical to the current/last value for that session. Writingmode:"normal"or the same custom title again on every open/quit is pure churn. - Sort "Recent chats" by the last in-file message
timestamp, not by file mtime. A metadata-only write should never reorder a conversation in a recency list, because the user didn't converse in it.
Steps to Reproduce
Minimal repro (one click, no typing):
- Pick a
~/.claude/projects/<project>/<uuid>.jsonlwhose newest message is old. Note its mtime:stat -f '%Sm' <file>. - In Claude Desktop, click that chat to open it. Type nothing.
- Re-stat: mtime is now "just now", and the file has grown.
tail -2shows a freshly-appended{"type":"mode","mode":"normal","sessionId":…}and/or{"type":"custom-title","customTitle":"…","sessionId":…}— identical to records already present earlier in the file. In the VS Code extension's Recent chats, the chat has jumped to the top.
Also reproduces on app quit, across multiple files at once: snapshot mtimes while running, then Cmd+Q without opening anything. Every recently-loaded session's JSONL is rewritten — mtimes bumped to the exact same second (a single flush, not per-chat activity), each appending the same redundant records. I observed this on files whose newest in-file message was from March, with zero ai-title events in them — so it's not specific to ai-title regeneration; it's redundant re-append of any bookkeeping record that already matches its prior value.
Environment
- Claude Code CLI: 2.1.91 (the bug surfaces in Claude Desktop + the VS Code extension, not the CLI itself; this form is CLI-shaped, so this field is best-effort)
- Claude Desktop: 1.14271.0
- OS: macOS 15.7.4 (build 24G517)
- Surface: Claude Code VS Code extension ("Recent chats" picker)
The appended records carry no message content, so transcripts stay valid — the harm is (a) mtime churn reordering Recent chats and (b) unbounded growth of duplicate records.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗