vscode-chat-audit
VS Code Copilot Chat Session Audit & Recovery
Use this skill when the user asks:
- "What was I working on in yesterday's sessions?"
- "I accidentally closed my chat tabs — help me find them"
- "Summarise all my recent chat sessions for this workspace"
- "Which session had the [topic] work?"
1. Storage Locations
Session JSONL files (primary source)
VS Code writes one .jsonl per chat session under the workspace-scoped storage:
%APPDATA%\Code\User\workspaceStorage\<workspace-hash>\chatSessions\<session-uuid>.jsonl
- Each
.jsonlfilename IS the session UUID (e.g.,43aad28a-d5c8-446d-aefc-dd9aa0b92002.jsonl) - The workspace hash differs per workspace folder path
- A code-workspace file gets its own hash, separate from the folder hash
Finding the correct workspace hash
$base = "$env:APPDATA\Code\User\workspaceStorage"
Get-ChildItem $base -Directory |
Where-Object { $_.LastWriteTime -ge (Get-Date).AddDays(-2) } |
ForEach-Object {
$wj = Join-Path $_.FullName 'workspace.json'
if (Test-Path $wj) { "$($_.Name): $(Get-Content $wj -Raw)" }
}
workspace.json contains {"folder":"file:///s%3A/NotionArchive"} or similar — match this to the target workspace path.
A multi-root .code-workspace file gets its own separate hash. Check both:
- The folder hash (e.g., maps to
s:\NotionArchive) - The
.code-workspacefile hash (preferred if the user opens via workspace file)
2. JSONL Transaction Log Format
The .jsonl files are not raw turn logs. Each line is a transaction entry. Three kinds:
kind |
Meaning | Key fields |
|---|---|---|
0 |
Initial snapshot — full state at creation time | v: dict with requests: [], customTitle, etc. |
1 |
Single-key replacement | k: ["fieldName"], v: newValue (e.g., title update) |
2 |
Array append | k: ["requests"], v: [list_of_new_request_objects] |
Reconstruct user turns
Only kind=2 with k=["requests"] matters for message extraction:
import json, os
from datetime import datetime, timezone, timedelta
LOCAL_TZ = timezone(timedelta(hours=11)) # adjust to user's timezone
def parse_session(path: str) -> dict:
title = ""
requests = []
with open(path, encoding="utf-8") as fh:
for line in fh:
line = line.strip()
if not line:
continue
obj = json.loads(line)
kind = obj.get("kind")
k = obj.get("k", [])
v = obj.get("v")
# Initial title from snapshot
if kind == 0 and isinstance(v, dict):
title = v.get("customTitle", "")
# Title update via kind=1
elif kind == 1 and k == ["customTitle"]:
title = v or title
# User turns via kind=2 appends
elif kind == 2 and k == ["requests"] and isinstance(v, list):
for req in v:
if not isinstance(req, dict) or "message" not in req:
continue
msg = req["message"]
text = msg.get("text", "") if isinstance(msg, dict) else str(msg)
ts = req.get("timestamp")
dt = datetime.fromtimestamp(ts / 1000, tz=LOCAL_TZ) if ts else None
requests.append({"text": text, "ts": ts, "dt": dt})
ts_start = next((r["dt"] for r in requests if r["dt"]), None)
ts_end = next((r["dt"] for r in reversed(requests) if r["dt"]), None)
return {
"title": title,
"requests": requests,
"turns": len(requests),
"ts_start": ts_start,
"ts_end": ts_end,
}
3. Enumerate and Date-Filter Sessions
def list_sessions(chat_dir: str, since: datetime = None) -> list[dict]:
results = []
for fname in os.listdir(chat_dir):
if not fname.endswith(".jsonl"):
continue
path = os.path.join(chat_dir, fname)
session_id = fname[:-6] # strip .jsonl
mtime = datetime.fromtimestamp(os.path.getmtime(path), tz=LOCAL_TZ)
data = parse_session(path)
data["session_id"] = session_id
data["mtime"] = mtime
data["size_kb"] = os.path.getsize(path) // 1024
if since and (data["ts_start"] or mtime) < since:
continue
results.append(data)
return sorted(results, key=lambda d: d["ts_start"] or d["mtime"])
4. Keyword Scanning (Find a Specific Session)
When the user describes a session by topic but doesn't know which UUID, scan all message text:
TARGET_KEYWORDS = [
"staging", "sqlite", "restore", "bak", "uuid",
"test db", "test database", "vm", "migration",
# add domain-specific terms
]
def score_session(session: dict, keywords: list[str]) -> list[str]:
hits = []
for req in session["requests"]:
text = req["text"].lower()
for kw in keywords:
if kw in text and kw not in hits:
hits.append(kw)
return hits
Print all sessions with any keyword hits:
for s in sessions:
hits = score_session(s, TARGET_KEYWORDS)
if hits:
date_str = s["ts_start"].strftime("%b %d %H:%M") if s["ts_start"] else "?"
print(f"{s['session_id'][:8]} {date_str} {s['size_kb']:>6}KB "
f"turns={s['turns']:<3} [{', '.join(hits[:5])}] {s['title'][:60]}")
5. Classification System
Once you have user turns, classify each session:
CONTINUE-NOW
The session has in-flight, incomplete work that requires immediate action.
Signals:
- A plan/task was started but not finished
- A script was run but results weren't resolved
- A blocker was hit and left unaddressed
- The final turn ends mid-task (no wrap-up)
- Files were modified, actions were taken, but not committed to a plan
CONTINUE-LATER
Work is paused but coherent; all current actions are complete but a next step is clear.
Signals:
- Bugs are identified but explicitly deferred
- Research was done but no implementation started
- A plan was created and steps were staged but no execution began
- User said "skip this for now", "come back to it", or "defer"
DOC-ONLY
The session is complete or its outcomes are fully captured elsewhere.
Signals:
- Plan was fully created and work was handed off to another session/agent
- Session ended cleanly with a summary or continuation prompt generated
- 84-turn session but all decisions are already in active plans or files
- The session is so old (no current plans reference it) that it's historical only
- Session crashed immediately (1 turn) — the plan it was continuing still exists
6. Output Format
session_summaries.txt — top-level index
=== SESSION: <short-id> (<N> KB) ===
[1] <first user message> ...
[2] <second user message> ...
...
Useful for a quick scan of all sessions and their message history.
session_docs/INDEX.md — curated index
# Crashed Session Recovery Index
**Generated:** YYYY-MM-DD
**Source:** N sessions from `<workspace>` workspace
## Priority: CONTINUE NOW
| File | Session | Topic | Key Plans |
...
## Priority: CONTINUE LATER
...
## Priority: DOCUMENTATION ONLY
...
## Cross-Session Plan References
| Plan ID | Title | Referenced In |
...
## Infrastructure Quick Reference
| Resource | Value |
...
session_docs/<CLASS>_<session-id-prefix>_<slug>.md — individual session docs
Naming: CONTINUE-NOW_43aad28a_uuid-migration.md
Each file should contain:
- Session metadata — ID, date/time, turn count, workspace
- Topic summary — 2–3 sentence overview
- Key technical decisions — bullet list
- In-flight work — what was left incomplete (for CONTINUE-NOW)
- Plan references — any plan IDs created or referenced
- Continuation prompt — ready-to-paste message to resume the session
7. Practical Workflow
- Find workspace hashes — scan
workspaceStoragefor recently modified folders matching the target workspace viaworkspace.json - List all session JSONL files — enumerate
chatSessions/under each matching hash - Parse and date-filter — run
parse_session()on each, keep sessions in the target date range - Quick overview — print session_id, title, time, turns, size for all sessions
- User confirmation — if the user isn't sure which session they're looking for, print the first 3 user messages from each candidate
- Keyword scan — if searching for a specific topic, scan message text for domain keywords
- Read full turns — for identified sessions, print all user messages with timestamps
- Classify — apply CONTINUE-NOW / CONTINUE-LATER / DOC-ONLY to each session
- Write docs — create INDEX.md and individual session doc files
Tip: Always check the
.code-workspace-scoped storage hash separately from the folder-scoped one. If the user opens VS Code via a.code-workspacefile, all sessions go to the workspace-file hash, not the folder hash.
8. Global vs Workspace Storage
| Location | Contains | Use for |
|---|---|---|
workspaceStorage/<hash>/chatSessions/ |
Full session JSONL (messages + structure) | Primary recovery source |
workspaceStorage/<hash>/state.vscdb |
Session index, metadata (SQLite) | Finding session IDs only; key is chat.ChatSessionStore.index |
globalStorage/state.vscdb |
Cross-workspace preferences | Rarely needed for session recovery |
workspaceStorage/<hash>/GitHub.copilot-chat/chat-session-resources/ |
Buffered large MCP tool response payloads | Not session content — these are MCP overflow buffers |
Do NOT confuse the
chat-session-resourcesfolder with actual session history. It's VS Code's internal buffer for oversized MCP tool responses.
9. SQLite Session Index (Optional)
If the JSONL files aren't found yet and you need to identify session IDs:
import sqlite3, json
db_path = r"C:\Users\<user>\AppData\Roaming\Code\User\workspaceStorage\<hash>\state.vscdb"
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("SELECT value FROM ItemTable WHERE key = 'chat.ChatSessionStore.index'")
row = cur.fetchone()
if row:
index = json.loads(row[0])
for session in index.get("sessions", []):
print(session) # contains sessionId, title, lastMessageDate
conn.close()
This gives you session IDs to match to JSONL filenames. However, the JSONL files themselves are more authoritative — parse them directly whenever possible.
More from ds-codi/project-memory-mcp
pyside6-mvc
Use this skill when building Python desktop applications using PySide6 with strict MVC architecture where all UI is defined by .ui files. Covers architecture patterns, controller/model/view separation, signal handling, and .ui file workflows.
95pyside6-qml-views
Use this skill when creating QML view files, designing QML component hierarchies, building layouts, styling QML controls, creating reusable QML components, implementing QML navigation / page switching, or working with QML resources. Covers QML file structure, component patterns, Material/Controls styling, resource management, and common QML idioms for desktop applications.
49pyside6-qml-architecture
Use this skill when creating a new PySide6 + QML desktop application with MVC architecture, setting up project structure, implementing the application bootstrap / DI container, or understanding how the MVC layers connect. Covers project scaffolding, entry points, singleton application class, service locator, signal registry, and lifecycle management.
47vscode-chat-response-stream
Use this skill when building VS Code chat participant responses using ChatResponseStream. Covers markdown, command buttons, command links, file trees, progress messages, references/anchors, the generic push() method, and ChatFollowupProvider for suggested follow-ups.
3cxxqt-rust-gui
Use this skill when building Qt-based GUI applications in Rust using CxxQt. Covers project structure, QObject integration, QML bindings, signal/slot patterns, and build configuration with cxx-qt-build.
3copilot-sdk
Build agentic applications with GitHub Copilot SDK. Use when embedding AI agents in apps, creating custom tools, implementing streaming responses, managing sessions, connecting to MCP servers, or creating custom agents. Triggers on Copilot SDK, GitHub SDK, agentic app, embed Copilot, programmable agent, MCP server, custom agent.
3