[BUG] Conversation history is lost in VSCode extension

Open 💬 25 comments Opened Feb 26, 2026 by I571664

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?

Using VSCode extension, conversation history disappears. In the most recent instance, had long conversation, closed the chat tab, re-opened it, and only part of the messages were present.

It's the integrated chat tab, not the terminal

What Should Happen?

Conversation history should not be lost.

Error Messages/Logs

N/A

Steps to Reproduce

Just use the VSCode Extension, the problem is recurrent.

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.59

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

25 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/9258
  2. https://github.com/anthropics/claude-code/issues/24172
  3. https://github.com/anthropics/claude-code/issues/12872

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

M1st3Rn0Rm4n · 4 months ago

Same observation. I have been losing my conversations when using Claude Code extension in VS Code. The conversations are still accessible by Claude Code CLI but it cannot be force ported/loaded back into the Claude Code extension. Is this some cheap shot attempt by Anthropic to force us into using their shitty Claude Code CLI?

davehornik · 4 months ago

Started happening to me lately. VS Code not remembering sh*t from past conversations, and even completely vanishing from folder.
Thinking about dropping out from my Max subscription because of Claude unreliability - if that's what Anthropic wants - I will comply... It is super frustrating to loose a conversation I was working on while I was in my office > came home, opened the same project folder, on the same device and it is gone.

JoeNewYork · 4 months ago

same issue, missing history after extension update

anthonyAtGingerLabs · 4 months ago

Same for me. Claude says they can see the full history on their side, but all of my past conversations are blank when I open them.

zhimin-z · 4 months ago

same for me...

landoncope · 4 months ago

Same for me. I actually prefer the extension to terminal, but this bug makes the extension unusable for my workflow.

CptOverdraft · 4 months ago

Same issue on macOS. Additionally seeing <task-notification> XML
appearing as session titles in the sessions list when background
agents complete. VSCode extension version: latest. Sessions accessible
via CLI but disappear from extension on restart.

KitcoVincentTu · 3 months ago

Same issue for me as well, macbook air with vscode extension. I've purchased the max plan.
Any update for this bug? there's one major conversation seems just gone forever.

CptOverdraft · 3 months ago

I think I partially resolved this issue. Here’s what I found:

Root cause (in my case):
I had previously started sessions in different workspaces and later added those folders to my current workspace via “Add Folder to Workspace.” This caused warnings/errors in the Problems panel, which I believe put VSCode into an inconsistent state on startup — leading to conversation history not appearing.

What fixed it:
After removing the folders that were causing issues in the Problems panel from my workspace, the problem was partially resolved. I can now see previous conversations when I restart VSCode.

Important note: Conversations are never actually lost — they’re saved independently of VSCode under the ~/.claude/projects/ directory. You can ask Claude Code to find your most recent conversation in ~/.claude/projects/…, show decisions, or extract any chat history data you need.

Tip: I also recommend keeping session notes and asking Claude Code to update them as conversations get long. In new sessions, you can reference those same note files to pick up where you left off with fresh context.

KitcoVincentTu · 3 months ago
I think I partially resolved this issue. Here’s what I found: Root cause (in my case): I had previously started sessions in different workspaces and later added those folders to my current workspace via “Add Folder to Workspace.” This caused warnings/errors in the Problems panel, which I believe put VSCode into an inconsistent state on startup — leading to conversation history not appearing. What fixed it: After removing the folders that were causing issues in the Problems panel from my workspace, the problem was partially resolved. I can now see previous conversations when I restart VSCode. Important note: Conversations are never actually lost — they’re saved independently of VSCode under the ~/.claude/projects/ directory. You can ask Claude Code to find your most recent conversation in ~/.claude/projects/…, show decisions, or extract any chat history data you need. Tip: I also recommend keeping session notes and asking Claude Code to update them as conversations get long. In new sessions, you can reference those same note files to pick up where you left off with fresh context.

That's a great advice, and i just checked it. Indeed, after re-opened the specific folder, and the conversation was back.

zachmiltz · 3 months ago

I am also having this bug. It is extremely frustrating. Should be a high priority.

I have to say this is not the case for me:

Important note: Conversations are never actually lost — they’re saved independently of VSCode under the ~/.claude/projects/ directory. You can ask Claude Code to find your most recent conversation in ~/.claude/projects/…, show decisions, or extract any chat history data you need.

I presume something is corrupted and I would like Claude to have prevention of this built in. The work is important and it was now lost. Why? It isn't clear. That's unacceptable.

The --resume does not find it either. Why?

CptOverdraft · 3 months ago
I am also having this bug. It is extremely frustrating. Should be a high priority. I have to say this is not the case for me: > Important note: Conversations are never actually lost — they’re saved independently of VSCode under the ~/.claude/projects/ directory. You can ask Claude Code to find your most recent conversation in ~/.claude/projects/…, show decisions, or extract any chat history data you need. I presume something is corrupted and I would like Claude to have prevention of this built in. The work is important and it was now lost. Why? It isn't clear. That's unacceptable. The --resume does not find it either. Why?

Sorry to hear that — losing work is indeed frustrating.

A few things that might help track down what happened:

Check if the session files actually exist on disk:

ls -lt ~/.claude/projects/*/sessions/ | head -20

This will list the most recent session files. If they're there, the data isn't lost — it's just not being picked up by the extension.

If the files exist, try resuming by session ID directly:

# Find your session IDs
ls ~/.claude/projects/YOUR_PROJECT/sessions/

# Then resume with the specific session ID
claude --resume SESSION_ID

If the files are missing or empty, that would point to a different root cause than what I experienced — possibly a write failure or a permissions issue. In that case, checking the following might help:

# Check permissions
ls -la ~/.claude/

# Check disk space
df -h ~

As a preventive measure going forward, I'd strongly recommend keeping a session notes file (e.g., SESSION_NOTES.md in your project root) and periodically asking Claude Code to update it with key decisions and progress. That way, even in a worst-case scenario, you have a recoverable checkpoint that doesn't depend on the session history at all.

mjstanton · 3 months ago

Same -has happened 2x today where I have been using Claude for a couple hours in VSCode with Claude official extension. The windows become unresponsive. I Reload window and when I click the clock icon there is no history at all. This is on Win 11 and I am connected to my WSL.

markwhitehead · 3 months ago

I threw Claude at this and sorted out a fix by patching the vs code extension JS. Works for me on Windows 11, hopefully it works for others.

It adds a fallback path for history/session loading:

  • If the extension’s normal session index returns nothing, it reads session .jsonl files directly from ~/.claude/projects/<mapped-cwd>/.
  • If opening a specific session returns empty, it directly loads <sessionId>.jsonl.
  • It restores list titles from ai-title records (or first user text).
  • It filters loaded records to message types the UI can render.

Patch file:
~/.vscode/extensions/anthropic.claude-code-2.1.101-win32-x64/extension.js

Replace the entire listSessions() method with:

async listSessions(){let K=this.cwd,V=await zd({dir:K,includeWorktrees:!1});if(V.length===0&&typeof K==="string"&&/^[a-z]:\\/.test(K)){let q=K[0].toUpperCase()+K.slice(1);if(q!==K){let z=await zd({dir:q,includeWorktrees:!1});z.length>0&&(this.logger.warn(`listSessions path-case fallback: ${K} -> ${q} (${z.length} sessions)`),K=q,V=z)}}let B=await g1.readTeleportMetadata(K,V.map((H)=>H.sessionId)),j=V.map((H)=>{let N=B.get(H.sessionId);return{id:H.sessionId,lastModified:H.lastModified,fileSize:H.fileSize,summary:H.summary,gitBranch:H.gitBranch,worktree:UR4(H.cwd),isCurrentWorkspace:!0,...N}});if(j.length===0)try{let q=require("os"),z=require("path"),U=require("fs"),Z=typeof K==="string"?K.replace(/[\\/:]/g,"-"):String(K),D=z.join(q.homedir(),".claude","projects"),L=z.join(D,Z),O=U.existsSync(L)?U.readdirSync(L).filter((F)=>F.endsWith(".jsonl")):[],N=O.map((F)=>{let M=z.join(L,F),A=U.statSync(M),w=F.replace(/\.jsonl$/,""),I=B.get(w),R=I?.summary;if(!R)try{let E=U.readFileSync(M,"utf8").split(/\r?\n/).filter((P)=>P.trim().length>0);for(let P of E){let T;try{T=JSON.parse(P)}catch{continue}if(T?.type==="ai-title"&&typeof T.aiTitle==="string"&&T.aiTitle.trim()){R=T.aiTitle.trim();break}if(!R&&T?.type==="user"){let h=T?.message?.content;if(Array.isArray(h)){let C=h.find((c)=>c?.type==="text"&&typeof c.text==="string"&&c.text.trim());if(C){R=C.text.trim(),R.length>70&&(R=R.slice(0,67)+"...")}}}}}catch{}return{id:w,lastModified:A.mtimeMs,fileSize:A.size,summary:R||w,gitBranch:I?.gitBranch,worktree:I?.worktree,isCurrentWorkspace:!0,...I}}),x=U.existsSync(L)?O.length:-1,G=U.existsSync(D)?U.readdirSync(D,{withFileTypes:!0}).filter((F)=>F.isDirectory()).map((F)=>F.name).slice(0,30):[];this.logger.warn(`listSessions debug cwd=${K} key=${Z} projectDir=${L} projectJsonl=${x} projectsRoot=${D} knownProjectDirs=${G.join(",")}`),N.length>0&&(this.logger.warn(`listSessions file fallback used: ${N.length} sessions from ${L}`),j=N)}catch(q){this.logger.warn(`listSessions debug failed: ${q}`)}ja(j);let Y=new Set(this.settings.getHiddenSessionIds()),X=Y.size>0?j.filter((H)=>!Y.has(H.id)):j;return X.length===0&&j.length>0&&(this.logger.warn(`listSessions fallback: hidden filter removed all sessions (cwd=${K}, total=${j.length}, hidden=${Y.size})`),X=j),this.logger.log(`listSessions cwd=${K} total=${j.length} hidden=${Y.size} returned=${X.length}`),{type:"list_sessions_response",sessions:X}}

Replace the entire getSession(K) method with:

async getSession(K){let V=this.cwd,B;try{B=await Ld(K,{dir:V})}catch(j){if(typeof V==="string"&&/^[a-z]:\\/.test(V)){let Y=V[0].toUpperCase()+V.slice(1);if(Y!==V)try{B=await Ld(K,{dir:Y}),this.logger.warn(`getSession path-case fallback: ${V} -> ${Y} (session=${K})`),V=Y}catch{}}if(!B)try{let Y=require("os"),X=require("path"),Q=require("fs"),G=(typeof V==="string"?V:String(V)).replace(/[\\/:]/g,"-"),q=X.join(Y.homedir(),".claude","projects",G,`${K}.jsonl`);if(Q.existsSync(q)){let z=Q.readFileSync(q,"utf8").split(/\r?\n/).filter((U)=>U.trim().length>0);B=[];for(let U of z)try{B.push(JSON.parse(U))}catch{}this.logger.warn(`getSession file fallback used: session=${K} file=${q} raw=${B.length}`)}}catch{}if(!B)throw j}if(!B||B.length===0)try{let Y=require("os"),X=require("path"),Q=require("fs"),G=(typeof V==="string"?V:String(V)).replace(/[\\/:]/g,"-"),q=X.join(Y.homedir(),".claude","projects",G,`${K}.jsonl`);if(Q.existsSync(q)){let z=Q.readFileSync(q,"utf8").split(/\r?\n/).filter((U)=>U.trim().length>0),U=[];for(let Z of z)try{U.push(JSON.parse(Z))}catch{}if(U.length>0)B=U,this.logger.warn(`getSession empty->file fallback used: session=${K} file=${q} raw=${B.length}`)}}catch{}let j;try{j=await(await g1.load(V,this.logger)).getSessionDiffs(K,V,B||[])}catch{j=void 0}let Y=(B||[]).filter((H)=>H?.type==="user"||H?.type==="assistant"||H?.type==="system"||H?.type==="meta"||H?.type==="compact");return this.logger.log(`getSession id=${K} raw=${(B||[]).length} filtered=${Y.length} cwd=${V}`),{type:"get_session_response",messages:Y,sessionDiffs:j}}

Then run:

  • Developer: Reload Window or restart VS Code
CptOverdraft · 3 months ago
I threw Claude at this and sorted out a fix by patching the vs code extension JS. Works for me on Windows 11, hopefully it works for others. It adds a fallback path for history/session loading: If the extension’s normal session index returns nothing, it reads session .jsonl files directly from ~/.claude/projects/<mapped-cwd>/. If opening a specific session returns empty, it directly loads <sessionId>.jsonl. It restores list titles from ai-title records (or first user text). It filters loaded records to message types the UI can render. Patch file: ~/.vscode/extensions/anthropic.claude-code-2.1.101-win32-x64/extension.js Replace the entire listSessions() method with: async listSessions(){let K=this.cwd,V=await zd({dir:K,includeWorktrees:!1});if(V.length===0&&typeof K==="string"&&/^[a-z]:\\/.test(K)){let q=K[0].toUpperCase()+K.slice(1);if(q!==K){let z=await zd({dir:q,includeWorktrees:!1});z.length>0&&(this.logger.warn(listSessions path-case fallback: ${K} -> ${q} (${z.length} sessions)),K=q,V=z)}}let B=await g1.readTeleportMetadata(K,V.map((H)=>H.sessionId)),j=V.map((H)=>{let N=B.get(H.sessionId);return{id:H.sessionId,lastModified:H.lastModified,fileSize:H.fileSize,summary:H.summary,gitBranch:H.gitBranch,worktree:UR4(H.cwd),isCurrentWorkspace:!0,...N}});if(j.length===0)try{let q=require("os"),z=require("path"),U=require("fs"),Z=typeof K==="string"?K.replace(/[\\/:]/g,"-"):String(K),D=z.join(q.homedir(),".claude","projects"),L=z.join(D,Z),O=U.existsSync(L)?U.readdirSync(L).filter((F)=>F.endsWith(".jsonl")):[],N=O.map((F)=>{let M=z.join(L,F),A=U.statSync(M),w=F.replace(/\.jsonl$/,""),I=B.get(w),R=I?.summary;if(!R)try{let E=U.readFileSync(M,"utf8").split(/\r?\n/).filter((P)=>P.trim().length>0);for(let P of E){let T;try{T=JSON.parse(P)}catch{continue}if(T?.type==="ai-title"&&typeof T.aiTitle==="string"&&T.aiTitle.trim()){R=T.aiTitle.trim();break}if(!R&&T?.type==="user"){let h=T?.message?.content;if(Array.isArray(h)){let C=h.find((c)=>c?.type==="text"&&typeof c.text==="string"&&c.text.trim());if(C){R=C.text.trim(),R.length>70&&(R=R.slice(0,67)+"...")}}}}}catch{}return{id:w,lastModified:A.mtimeMs,fileSize:A.size,summary:R||w,gitBranch:I?.gitBranch,worktree:I?.worktree,isCurrentWorkspace:!0,...I}}),x=U.existsSync(L)?O.length:-1,G=U.existsSync(D)?U.readdirSync(D,{withFileTypes:!0}).filter((F)=>F.isDirectory()).map((F)=>F.name).slice(0,30):[];this.logger.warn(listSessions debug cwd=${K} key=${Z} projectDir=${L} projectJsonl=${x} projectsRoot=${D} knownProjectDirs=${G.join(",")}),N.length>0&&(this.logger.warn(listSessions file fallback used: ${N.length} sessions from ${L}),j=N)}catch(q){this.logger.warn(listSessions debug failed: ${q})}ja(j);let Y=new Set(this.settings.getHiddenSessionIds()),X=Y.size>0?j.filter((H)=>!Y.has(H.id)):j;return X.length===0&&j.length>0&&(this.logger.warn(listSessions fallback: hidden filter removed all sessions (cwd=${K}, total=${j.length}, hidden=${Y.size})),X=j),this.logger.log(listSessions cwd=${K} total=${j.length} hidden=${Y.size} returned=${X.length}),{type:"list_sessions_response",sessions:X}} Replace the entire getSession(K) method with: async getSession(K){let V=this.cwd,B;try{B=await Ld(K,{dir:V})}catch(j){if(typeof V==="string"&&/^[a-z]:\\/.test(V)){let Y=V[0].toUpperCase()+V.slice(1);if(Y!==V)try{B=await Ld(K,{dir:Y}),this.logger.warn(getSession path-case fallback: ${V} -> ${Y} (session=${K})),V=Y}catch{}}if(!B)try{let Y=require("os"),X=require("path"),Q=require("fs"),G=(typeof V==="string"?V:String(V)).replace(/[\\/:]/g,"-"),q=X.join(Y.homedir(),".claude","projects",G,${K}.jsonl);if(Q.existsSync(q)){let z=Q.readFileSync(q,"utf8").split(/\r?\n/).filter((U)=>U.trim().length>0);B=[];for(let U of z)try{B.push(JSON.parse(U))}catch{}this.logger.warn(getSession file fallback used: session=${K} file=${q} raw=${B.length})}}catch{}if(!B)throw j}if(!B||B.length===0)try{let Y=require("os"),X=require("path"),Q=require("fs"),G=(typeof V==="string"?V:String(V)).replace(/[\\/:]/g,"-"),q=X.join(Y.homedir(),".claude","projects",G,${K}.jsonl);if(Q.existsSync(q)){let z=Q.readFileSync(q,"utf8").split(/\r?\n/).filter((U)=>U.trim().length>0),U=[];for(let Z of z)try{U.push(JSON.parse(Z))}catch{}if(U.length>0)B=U,this.logger.warn(getSession empty->file fallback used: session=${K} file=${q} raw=${B.length})}}catch{}let j;try{j=await(await g1.load(V,this.logger)).getSessionDiffs(K,V,B||[])}catch{j=void 0}let Y=(B||[]).filter((H)=>H?.type==="user"||H?.type==="assistant"||H?.type==="system"||H?.type==="meta"||H?.type==="compact");return this.logger.log(getSession id=${K} raw=${(B||[]).length} filtered=${Y.length} cwd=${V}),{type:"get_session_response",messages:Y,sessionDiffs:j}} Then run: * Developer: Reload Window or restart VS Code

Nice patch! Looks solid — the file-fallback logic is platform-agnostic since the ~/.claude/projects/<cwd-mapped>/ mapping works the same on macOS/Linux (only the drive-letter case-fix block is Windows-specific, and it's a no-op elsewhere).

Two heads-ups for anyone trying it:

The minified identifiers (zd, Ld, g1, etc.) are version-bound — if your extension version differs from 2.1.101, grep for the method first and adapt.
Extension auto-updates will wipe the patch, so keep a backup of the modified extension.js.
Personally I sidestep the whole issue by keeping per-session notes in the repo (docs/session-notes/) — survives extension bugs, version bumps, and machine swaps. But for recovering existing lost history your patch is the right call. Thanks for sharing!

gjourdain · 3 months ago
I threw Claude at this and sorted out a fix by patching the vs code extension JS. Works for me on Windows 11, hopefully it works for others. It adds a fallback path for history/session loading: If the extension’s normal session index returns nothing, it reads session .jsonl files directly from ~/.claude/projects/<mapped-cwd>/. If opening a specific session returns empty, it directly loads <sessionId>.jsonl. It restores list titles from ai-title records (or first user text). It filters loaded records to message types the UI can render. Patch file: ~/.vscode/extensions/anthropic.claude-code-2.1.101-win32-x64/extension.js Replace the entire listSessions() method with: async listSessions(){let K=this.cwd,V=await zd({dir:K,includeWorktrees:!1});if(V.length===0&&typeof K==="string"&&/^[a-z]:\\/.test(K)){let q=K[0].toUpperCase()+K.slice(1);if(q!==K){let z=await zd({dir:q,includeWorktrees:!1});z.length>0&&(this.logger.warn(listSessions path-case fallback: ${K} -> ${q} (${z.length} sessions)),K=q,V=z)}}let B=await g1.readTeleportMetadata(K,V.map((H)=>H.sessionId)),j=V.map((H)=>{let N=B.get(H.sessionId);return{id:H.sessionId,lastModified:H.lastModified,fileSize:H.fileSize,summary:H.summary,gitBranch:H.gitBranch,worktree:UR4(H.cwd),isCurrentWorkspace:!0,...N}});if(j.length===0)try{let q=require("os"),z=require("path"),U=require("fs"),Z=typeof K==="string"?K.replace(/[\\/:]/g,"-"):String(K),D=z.join(q.homedir(),".claude","projects"),L=z.join(D,Z),O=U.existsSync(L)?U.readdirSync(L).filter((F)=>F.endsWith(".jsonl")):[],N=O.map((F)=>{let M=z.join(L,F),A=U.statSync(M),w=F.replace(/\.jsonl$/,""),I=B.get(w),R=I?.summary;if(!R)try{let E=U.readFileSync(M,"utf8").split(/\r?\n/).filter((P)=>P.trim().length>0);for(let P of E){let T;try{T=JSON.parse(P)}catch{continue}if(T?.type==="ai-title"&&typeof T.aiTitle==="string"&&T.aiTitle.trim()){R=T.aiTitle.trim();break}if(!R&&T?.type==="user"){let h=T?.message?.content;if(Array.isArray(h)){let C=h.find((c)=>c?.type==="text"&&typeof c.text==="string"&&c.text.trim());if(C){R=C.text.trim(),R.length>70&&(R=R.slice(0,67)+"...")}}}}}catch{}return{id:w,lastModified:A.mtimeMs,fileSize:A.size,summary:R||w,gitBranch:I?.gitBranch,worktree:I?.worktree,isCurrentWorkspace:!0,...I}}),x=U.existsSync(L)?O.length:-1,G=U.existsSync(D)?U.readdirSync(D,{withFileTypes:!0}).filter((F)=>F.isDirectory()).map((F)=>F.name).slice(0,30):[];this.logger.warn(listSessions debug cwd=${K} key=${Z} projectDir=${L} projectJsonl=${x} projectsRoot=${D} knownProjectDirs=${G.join(",")}),N.length>0&&(this.logger.warn(listSessions file fallback used: ${N.length} sessions from ${L}),j=N)}catch(q){this.logger.warn(listSessions debug failed: ${q})}ja(j);let Y=new Set(this.settings.getHiddenSessionIds()),X=Y.size>0?j.filter((H)=>!Y.has(H.id)):j;return X.length===0&&j.length>0&&(this.logger.warn(listSessions fallback: hidden filter removed all sessions (cwd=${K}, total=${j.length}, hidden=${Y.size})),X=j),this.logger.log(listSessions cwd=${K} total=${j.length} hidden=${Y.size} returned=${X.length}),{type:"list_sessions_response",sessions:X}} Replace the entire getSession(K) method with: async getSession(K){let V=this.cwd,B;try{B=await Ld(K,{dir:V})}catch(j){if(typeof V==="string"&&/^[a-z]:\\/.test(V)){let Y=V[0].toUpperCase()+V.slice(1);if(Y!==V)try{B=await Ld(K,{dir:Y}),this.logger.warn(getSession path-case fallback: ${V} -> ${Y} (session=${K})),V=Y}catch{}}if(!B)try{let Y=require("os"),X=require("path"),Q=require("fs"),G=(typeof V==="string"?V:String(V)).replace(/[\\/:]/g,"-"),q=X.join(Y.homedir(),".claude","projects",G,${K}.jsonl);if(Q.existsSync(q)){let z=Q.readFileSync(q,"utf8").split(/\r?\n/).filter((U)=>U.trim().length>0);B=[];for(let U of z)try{B.push(JSON.parse(U))}catch{}this.logger.warn(getSession file fallback used: session=${K} file=${q} raw=${B.length})}}catch{}if(!B)throw j}if(!B||B.length===0)try{let Y=require("os"),X=require("path"),Q=require("fs"),G=(typeof V==="string"?V:String(V)).replace(/[\\/:]/g,"-"),q=X.join(Y.homedir(),".claude","projects",G,${K}.jsonl);if(Q.existsSync(q)){let z=Q.readFileSync(q,"utf8").split(/\r?\n/).filter((U)=>U.trim().length>0),U=[];for(let Z of z)try{U.push(JSON.parse(Z))}catch{}if(U.length>0)B=U,this.logger.warn(getSession empty->file fallback used: session=${K} file=${q} raw=${B.length})}}catch{}let j;try{j=await(await g1.load(V,this.logger)).getSessionDiffs(K,V,B||[])}catch{j=void 0}let Y=(B||[]).filter((H)=>H?.type==="user"||H?.type==="assistant"||H?.type==="system"||H?.type==="meta"||H?.type==="compact");return this.logger.log(getSession id=${K} raw=${(B||[]).length} filtered=${Y.length} cwd=${V}),{type:"get_session_response",messages:Y,sessionDiffs:j}} Then run: * Developer: Reload Window or restart VS Code

This is good, it actually helped me get back some of my conversation but it seems a lot were corrupted in the update... so ... yay (/S)...

I modified something else from your JS patch. Mine was having issues with drive letter being in lowercase vs uppercase. so I did :
Instead of :
`// in listSessions
Z=typeof K==="string"?K.replace(/[\/:]/g,"-"):String(K)

// in getSession
G=(typeof V==="string"?V:String(V)).replace(/[\/:]/g,"-")`

I did :
// in listSessions
Z=typeof K==="string"?K.replace(/[\\/:]/g,"-").toLowerCase():String(K).toLowerCase()
// in getSession
G=(typeof V==="string"?V:String(V)).replace(/[\\/:]/g,"-").toLowerCase()

But I also made two modifications to my system :

I created a task scheduler running every day at 18h00 : powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$source='C:\Users\myusername\.claude\projects'; $backupRoot='C:\Users\myusername \.CLAUDE-BACKUP'; $date=Get-Date -Format 'dd-MM-yyyy'; $destination=Join-Path $backupRoot ('projects-' + $date); New-Item -ItemType Directory -Path $destination -Force | Out-Null; robocopy $source $destination /E /R:2 /W:2"

which at lease give me some piece of mind.

And modified settings.json and added :

{
  "voiceEnabled": false,
  "effortLevel": "high",
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node C:/Users/**myusername**/.claude/save-conversation.js",
            "timeout": 15,
            "statusMessage": "Saving conversation to markdown..."
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node C:/Users/**myusername**/.claude/save-conversation.js",
            "async": true
          }
        ]
      }
    ]
  }
}

and used this save-conversation.js script :
`#!/usr/bin/env node
// Claude Code Stop hook: saves the current session to a markdown file in the workspace root.
const fs = require('fs');
const path = require('path');
const os = require('os');

let stdin = '';
process.stdin.on('data', d => (stdin += d));
process.stdin.on('end', () => {
try {
const input = JSON.parse(stdin || '{}');
const sessionId = input.session_id;
if (!sessionId) process.exit(0);

// Find the JSONL file by scanning all project directories
const projectsBase = path.join(os.homedir(), '.claude', 'projects');
let jsonlFile = null;
for (const proj of fs.readdirSync(projectsBase)) {
const candidate = path.join(projectsBase, proj, ${sessionId}.jsonl);
if (fs.existsSync(candidate)) {
jsonlFile = candidate;
break;
}
}
if (!jsonlFile) process.exit(0);

const lines = fs.readFileSync(jsonlFile, 'utf8').trim().split('\n').filter(Boolean);

let title = null;
let date = new Date().toISOString().substring(0, 10);
const messages = [];

for (const line of lines) {
try {
const d = JSON.parse(line);
if (d.type !== 'user' && d.type !== 'assistant') continue;

let content = d.message?.content || '';
if (Array.isArray(content)) {
content = content
.filter(c => c && c.type === 'text')
.map(c => c.text)
.join('\n');
}
content = String(content).trim();

// Strip IDE context tags from user messages
content = content.replace(/<ide_opened_file>[\s\S]*?<\/ide_opened_file>/g, '').trim();
content = content.replace(/<ide_selection>[\s\S]*?<\/ide_selection>/g, '').trim();
content = content.replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, '').trim();

if (!content) continue;

if (!title && d.type === 'user') {
// Use the first real user message as the title
title = content.substring(0, 60).replace(/[<>:"/\\|?*\n\r]/g, ' ').trim().replace(/\s+/g, '-');
}

const role = d.type === 'user' ? 'You' : 'Claude';
const ts = d.timestamp ? new Date(d.timestamp).toISOString().substring(0, 16).replace('T', ' ') : '';
messages.push({ role, content, ts });
} catch (e) {}
}

if (!messages.length) process.exit(0);

title = (title || sessionId.substring(0, 8)).substring(0, 80);

let md = # ${title}\n\n_${date} · session ${sessionId.substring(0, 8)}_\n\n;
for (const m of messages) {
md += ---\n\n### ${m.role}${m.ts ? <sub>${m.ts}</sub> : ''}\n\n${m.content}\n\n;
}

// Write to the current working directory (workspace root)
const outDir = process.env.PWD
? process.env.PWD.replace(/^\/([a-zA-Z])\//, '$1:/').replace(/\//g, '\\')
: process.cwd();

const outFile = path.join(outDir, ${title}.md);
fs.writeFileSync(outFile, md, 'utf8');

console.log(JSON.stringify({ systemMessage: Conversation saved → ${path.basename(outFile)} }));
} catch (e) {
process.exit(0);
}
});
`

so far so good... it's not the best but at least I'm "protected"

judahthornewillx50 · 2 months ago

+1 — this is a significant friction point for multi-tab workflows.

Concrete scenario: I routinely run 6–10 parallel chat tabs when executing a complex creation wizard (each tab handling a different step / sub-deliverable, with carefully curated context in each). Laptop sleeps overnight → morning reload of VS Code → 1 tab visible, 7–9 gone from the sidebar. The .jsonl transcripts are all intact on disk at ~/.claude/projects/.../, and the session history picker can re-open them one at a time — but I've lost the mental map of which tabs were active, in what order, doing what. Re-opening them via the picker is possible but requires remembering 8 UUIDs and what each was working on; after a night's sleep, that context is gone.

What would solve this: persist the set of currently-open tabs per-workspace, and restore them (as tabs in the sidebar, in the same order) on window reload — same behavior VS Code itself provides for editor tabs via window.restoreWindows.

Related papercut in the same workflow: when I manually rename a tab (e.g. to a project-specific identifier like s1.n19.d01-wyrm-paper), the extension auto-overwrites it with its own generated title. Manually-set tab titles should stick. This compounds the persistence issue because even the titles I do see in the session history picker aren't the names I gave them — making the 8-tab mental map even harder to rebuild.

Environment: Claude Code VS Code extension 2.1.118, VS Code connecting via Remote-SSH to a remote vscode-server (Hetzner VPS). This did not bite me while on GitHub Codespaces — presumably because the Codespaces host preserved extension state across browser reloads. After migrating to local-VS-Code → remote vscode-server, the gap became constant.

Related: #40877 (sessions exist on disk but missing from sidebar).

lelikg · 2 months ago

macOS user here, also losing messages from the VSCode extension. Worth flagging that this thread mixes up two different bugs.

I scanned my session files under ~/.claude/projects/<encoded-cwd>/. Found cases where the assistant fired off a tool call but the tool result never got saved, even though I remember the tool running and the conversation continuing for a while after. Here's the tail of one of them:

[615] assistant  14:41:36  tool_use: Bash (id A)
[616] user       14:41:36  tool_result for A
[617] assistant  14:41:41  text: "Let me check..."
[618] assistant  14:41:42  tool_use: Bash (id B)   <- no tool_result anywhere
[619] last-prompt          (extension metadata, no timestamp)
[620] ai-title             (extension metadata, no timestamp)

Everything after that orphaned tool_use is just extension metadata written when I reopened the chat later. The bash result and all the assistant turns after it are gone.

To rule out "the data is fine, the UI just isn't showing it", I imported one of these into Zed (which reads the same .jsonl). Zed showed the same truncated conversation. So claude --resume and any other tool will see the same gap.

Orphan tool calls are detectable since they leave a trace. When the lost tail is plain text it disappears silently, so it's probably happening more often than I can confirm.

macOS Darwin 25.1.0, latest extension as of 2026-05-09, Anthropic API.

patrizok · 1 month ago

Claude Code history not loading for projects on SMB-mapped network drives (Windows)
Claude Code v2.1.145 on Windows, VSCode extension.
When working on a project located on a mapped network drive (SMB share, e.g. P:\project_xxxx → \\server\share\...), session history is not listed in the UI after reopening VSCode. The .jsonl session files are correctly written to %USERPROFILE%\.claude\projects\p--project-xxxx\ and persist across sessions — they are not deleted. However, none of them appear in the history/resume list.
Identical projects stored on a local drive (e.g. C:\Users\PC\Desktop\project_xxxx) work correctly: all past sessions appear in the history list.

CptOverdraft · 1 month ago

Kept hitting this too — recent sessions silently missing from the VSCode
history panel. Since Claude Code writes every session to disk anyway at
~/.claude/projects/<project>/<session-id>.jsonl, I put together a small
read-only tool that reads those files directly and bypasses the UI cache:

👉 https://github.com/CPTOD-Labs/claude-history

  • Python 3, stdlib only, zero dependencies
  • list — every session, including the ones missing from the panel
  • last — Claude's last message from your most recent session, ready to

paste into a new chat to pick up where you left off

  • show / grep for browsing & search
  • Strictly read-only: never writes/moves/deletes anything under ~/.claude/

Doesn't fix the root cause (cache/index drift), but at least nothing is
actually lost — the data is on disk, this just surfaces it. Hope it helps
someone until there's an official fix.

halvors · 1 month ago

Is this going to be fixed anytime soon?

adrienberger-DSA · 28 days ago

Same problem on a different claude version. Adding the information as a comment so as to not create a separate issue:

Title: Session history empty in VSCode sidebar after crash during extension upgrade (apiKeyHelper setup)

Summary

After a VSCode crash that occurred during an extension upgrade (2.1.179 → 2.1.181), the session history panel (clock icon in sidebar) shows no past sessions — only the currently active one. Session files are intact on disk and resumable via CLI (claude --resume <id>), but the sidebar never displays them.

Environment

  • OS: WSL2 (Ubuntu) on Windows — Linux 6.6.87.2-microsoft-standard-WSL2
  • VSCode: Remote - WSL
  • Extension version: 2.1.181 (upgraded from 2.1.178 → 2.1.179 → 2.1.181 around the crash)
  • CLI version: 2.1.170
  • Auth method: apiKeyHelper (corporate proxy, no OAuth/claude.ai login)
  • Base URL: Custom corporate proxy via ANTHROPIC_BASE_URL
  • Project directory: /home/<user>/code/<project>

Steps to reproduce

  1. Use Claude Code extension with apiKeyHelper auth (no OAuth/claude.ai login)
  2. Have multiple sessions (52 .jsonl files in ~/.claude/projects/<encoded-path>/)
  3. VSCode crashes during extension auto-update (2.1.179 → 2.1.181)
  4. Reopen VSCode — sidebar history is empty

What I expected

All 52 sessions should appear in the clock-icon history panel, as they did before the crash (confirmed working on 2.1.178, June 16–17).

What actually happens

  • History panel shows only the current active session
  • Tried: window reload, extension host restart, hide/re-show sidebar, disable/re-enable extension, full uninstall/reinstall — none restore the history
  • claude --resume <session-id> from CLI works perfectly — sessions are intact

Diagnostic findings

  1. Files are valid: All 52 .jsonl session files exist in ~/.claude/projects/<encoded-path>/ with correct format (valid UUID filenames, contain ai-title, lastPrompt metadata entries, isSidechain: false)
  1. Extension scans correct path: Confirmed via source analysis that listSessions() calls Wre({dir: this.cwd}) which resolves to the correct project directory and scans *.jsonl files directly from disk (no sessionStore override)
  1. No filtering issue: isCurrentWorkspace evaluates to true for all sessions (no worktree, same cwd). No hiddenSessionIds found in globalState.
  1. Logs confirm requests are made: list_sessions_request messages appear in extension logs at each panel open, but no visible error is logged for the response
  1. Auth note: Extension logs show "No authentication found" (expected — using apiKeyHelper, not OAuth). "No OAuth token available for teleport" errors appear for remote session features but local session listing uses the disk-based path (jAe) not the store-based path (I2e)
  1. History worked before: Extension logs from June 10–17 show many successful resume: operations triggered from the sidebar across versions 2.1.169–2.1.178

Simulated listing works

Running the equivalent of the extension's PY() function in Python against all 52 session files: all return valid display text (via aiTitle or lastPrompt in the file tail). None are filtered by isSidechain, missing metadata, or invalid UUID. The extension should display all of them.

Hypothesis

The crash during the extension upgrade may have corrupted the webview's persisted state (setState/getState), which now contains a stale/empty session list that takes priority over fresh disk scans. Since there's no user-accessible way to clear this state (no "Reset History" command, no identifiable file on disk for remote/WSL setups), the history remains permanently broken.

Alternatively, the upgrade from 2.1.178 to 2.1.179+ may have introduced a regression in session listing for apiKeyHelper-only setups (no OAuth token).

Workaround

Sessions are resumable via CLI:

claude --resume <session-id>

Feature request

A "Clear/Reset session history state" command in the command palette would allow users to recover from this kind of corruption without reinstalling.

szabodanika · 27 days ago

Same happening here. Regularly losing hours of work due to accidentally closing a tab.

bishwoconpago · 20 days ago

What a struggle....

So with VSCode Claude code extension. We need to remember which project was added to the workspace in the beginning during your planning session. Because Claude stores session inside that project (in ~/.claude/projects). It does not matter how many other projects are in your current workspace.

It still bugs out every now and then failing to process even simple commands like "Hi".