You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CSM already records every session that has ever exited, and every line those sessions printed. There is no way to look at any of it. Add a history browser: list past sessions, filter them, open one to read its output, and relaunch it.
The archive already exists — it is just write-mostly
Two tables in output.db hold this today:
Table
Contents
Written by
session_history
one row per exited session: session_id, session_name, working_folder, command, args, group_id, exited_at, snapshot_json
on session exit
session_output
every ANSI-stripped output line: session_id, session_name, ts, line, mirrored into the output_fts FTS5 index
OutputIndexer, continuously
session_history has indexes on session_id and working_folder, and snapshot_json carries Kind plus the SSH/WSL fields — enough to relaunch an entry as the right kind of session.
But nothing lists it.SearchService exposes exactly two reads of session_history, and both are keyed point lookups: GetSessionHistoryAsync(sessionId) and GetLatestSessionHistoryForFolderAsync(folderPath). There is no query that returns "the past sessions" in any order, filtered by anything. The table is only reachable if you already know which row you want.
The output side has the mirror-image gap: SearchAsync returns FTS5 snippets matching a query term. There is no way to read a past session's output as contiguous ordered lines — no WHERE session_id = ? ORDER BY ts path exists. So even having found an old session, you cannot read it; you can only search inside it for something you already suspect is there.
So both halves are missing, and both are small: the data, the schema and the indexes are already in place.
Desired behavior
A list of past sessions, newest first, showing name, working folder, command, when it exited, and its group.
Filters over that list: by working folder / repo, by command (e.g. only claude sessions), by group, by date range, and free-text over the session name. Folder and session-id already have indexes; the others need considering.
Open an entry to read its output as an ordered, scrollable transcript — not a snippet list.
Search within a single past session, distinct from today's global search.
Relaunch an entry, via the snapshot_json that already exists for this purpose.
Delete an entry and its output. DeleteSessionLogsAsync(sessionId) already does the output half.
The retention collision — the part worth deciding first
session_history rows and session_output lines have completely independent lifetimes, and nothing reconciles them:
PruneOldOutputAsync runs at startup and executes DELETE FROM session_output WHERE ts < $cutoff. It does not touch session_history.
So with the default 30-day retention, a session_history row from two months ago still exists, and its transcript does not. A history browser built naively would list it, the user would click it, and it would open empty.
This is not hypothetical: #124 reports output.db at 1.32 GB in live use and proposes size-capped FIFO eviction, which would make transcripts disappear faster and on a schedule the user cannot predict from a date.
So the browser has to treat "this session existed" and "its transcript is still retained" as two different facts, and show the difference honestly — a listed entry whose output has been evicted should say so, not present as a session with no output. Options worth weighing:
Show retention state per entry (transcript retained / evicted), derived from whether any session_output rows remain for that session_id.
Prune session_history on the same policy, so the list only ever shows what is readable — simpler, but throws away the cheap metadata (and the relaunch snapshot) along with the expensive lines.
Keep history forever, evict output, and make "relaunch" still work on evicted entries — the snapshot is tiny and does not depend on the transcript.
The third is probably right: session_history is small and useful on its own; session_output is what actually costs 1.3 GB. But this should be an explicit decision rather than a side effect, and it interacts directly with #124.
Two overlapping "past sessions" concepts
Worth resolving in the UI rather than shipping a third list:
Recently closed — a ring buffer capped at 10, newest-first, in state.json, surfaced at the top of the New Session dialog and via Ctrl+Shift+T. Purpose: undo a close.
session_history — unbounded, in the DB, not surfaced anywhere. Purpose: the archive.
They overlap for the last 10 closes and diverge after that. The browser should either present a single coherent "past sessions" view that the ring is the recent head of, or make the distinction visible. Silently having two different answers to "what did I close?" is the outcome to avoid.
Key interfaces
SearchService owns the schema and every query, and every use of the shared connection must be serialized through DbGate (using var _ = await DbGate.AcquireAsync();) — the connection is handed to SearchServiceand every OutputIndexer and is not thread-safe. New queries here are the main work:
a listing query over session_history with filter predicates and paging — must page, since this table is unbounded by design;
an ordered transcript read over session_output for one session_id, also paged (a long Claude session is a lot of lines, and loading it all into a WPF control will not go well);
a scoped FTS query — output_fts MATCH ? restricted to one session_id — for search-within-session.
SessionHistoryEntry — the existing row model; may need a retention flag if the browser surfaces eviction state.
snapshot_json → relaunch.ReopenClosedSessionAsync is the existing precedent for rebuilding a session from a snapshot, including giving it a fresh Id so it is independent of anything still referencing the old one. Relaunch from history should reuse that behaviour, not invent a second one.
Where the UI lives.Claude history analytics + indexing across all sessions #66 already proposes a "History" / "Insights" panel for Claude transcript analytics. These should be one surface, not two panels both called history. Claude history analytics + indexing across all sessions #66 mines ~/.claude/projects/*.jsonl (Claude's own transcripts); this issue reads CSM's own session_output. Different sources, same question from the user's side — "what happened in my past sessions?"
Acceptance criteria
A list of past sessions is reachable from the UI, newest first, paged.
Filters by working folder, command, group and date range each narrow the list correctly, and combine.
Opening an entry shows its output as ordered contiguous lines, not FTS snippets, loaded incrementally rather than all at once.
Search within a single past session returns matches scoped to that session only.
An entry whose output has been pruned or evicted is shown as such, and does not present as an empty session.
Relaunching an entry produces a working session of the correct Kind (Local / SSH / WSL) with a fresh Id.
Deleting an entry removes both its history row and its output rows.
Every new query goes through DbGate, and none of them run on the UI thread.
The relationship to the recently-closed ring is coherent — no two lists giving different answers without explanation.
Out of scope
Restoring scrollback into a relaunched pane. Retention of xterm buffer across restart is already documented as out of scope; a relaunched session starts with an empty terminal. Reading the archived transcript is a separate view, not a rehydrated pane.
Summary
CSM already records every session that has ever exited, and every line those sessions printed. There is no way to look at any of it. Add a history browser: list past sessions, filter them, open one to read its output, and relaunch it.
The archive already exists — it is just write-mostly
Two tables in
output.dbhold this today:session_historysession_id,session_name,working_folder,command,args,group_id,exited_at,snapshot_jsonsession_outputsession_id,session_name,ts,line, mirrored into theoutput_ftsFTS5 indexOutputIndexer, continuouslysession_historyhas indexes onsession_idandworking_folder, andsnapshot_jsoncarriesKindplus the SSH/WSL fields — enough to relaunch an entry as the right kind of session.But nothing lists it.
SearchServiceexposes exactly two reads ofsession_history, and both are keyed point lookups:GetSessionHistoryAsync(sessionId)andGetLatestSessionHistoryForFolderAsync(folderPath). There is no query that returns "the past sessions" in any order, filtered by anything. The table is only reachable if you already know which row you want.The output side has the mirror-image gap:
SearchAsyncreturns FTS5 snippets matching a query term. There is no way to read a past session's output as contiguous ordered lines — noWHERE session_id = ? ORDER BY tspath exists. So even having found an old session, you cannot read it; you can only search inside it for something you already suspect is there.So both halves are missing, and both are small: the data, the schema and the indexes are already in place.
Desired behavior
claudesessions), by group, by date range, and free-text over the session name. Folder and session-id already have indexes; the others need considering.snapshot_jsonthat already exists for this purpose.DeleteSessionLogsAsync(sessionId)already does the output half.The retention collision — the part worth deciding first
session_historyrows andsession_outputlines have completely independent lifetimes, and nothing reconciles them:PruneOldOutputAsyncruns at startup and executesDELETE FROM session_output WHERE ts < $cutoff. It does not touchsession_history.session_historyrow from two months ago still exists, and its transcript does not. A history browser built naively would list it, the user would click it, and it would open empty.This is not hypothetical: #124 reports
output.dbat 1.32 GB in live use and proposes size-capped FIFO eviction, which would make transcripts disappear faster and on a schedule the user cannot predict from a date.So the browser has to treat "this session existed" and "its transcript is still retained" as two different facts, and show the difference honestly — a listed entry whose output has been evicted should say so, not present as a session with no output. Options worth weighing:
session_outputrows remain for thatsession_id.session_historyon the same policy, so the list only ever shows what is readable — simpler, but throws away the cheap metadata (and the relaunch snapshot) along with the expensive lines.The third is probably right:
session_historyis small and useful on its own;session_outputis what actually costs 1.3 GB. But this should be an explicit decision rather than a side effect, and it interacts directly with #124.Two overlapping "past sessions" concepts
Worth resolving in the UI rather than shipping a third list:
state.json, surfaced at the top of the New Session dialog and viaCtrl+Shift+T. Purpose: undo a close.session_history— unbounded, in the DB, not surfaced anywhere. Purpose: the archive.They overlap for the last 10 closes and diverge after that. The browser should either present a single coherent "past sessions" view that the ring is the recent head of, or make the distinction visible. Silently having two different answers to "what did I close?" is the outcome to avoid.
Key interfaces
SearchServiceowns the schema and every query, and every use of the shared connection must be serialized throughDbGate(using var _ = await DbGate.AcquireAsync();) — the connection is handed toSearchServiceand everyOutputIndexerand is not thread-safe. New queries here are the main work:session_historywith filter predicates and paging — must page, since this table is unbounded by design;session_outputfor onesession_id, also paged (a long Claude session is a lot of lines, and loading it all into a WPF control will not go well);output_fts MATCH ?restricted to onesession_id— for search-within-session.SessionHistoryEntry— the existing row model; may need a retention flag if the browser surfaces eviction state.snapshot_json→ relaunch.ReopenClosedSessionAsyncis the existing precedent for rebuilding a session from a snapshot, including giving it a fresh Id so it is independent of anything still referencing the old one. Relaunch from history should reuse that behaviour, not invent a second one.~/.claude/projects/*.jsonl(Claude's own transcripts); this issue reads CSM's ownsession_output. Different sources, same question from the user's side — "what happened in my past sessions?"Acceptance criteria
Kind(Local / SSH / WSL) with a fresh Id.DbGate, and none of them run on the UI thread.Out of scope
Related
output.dbat 1.32 GB, FIFO size eviction; determines what is still readable