Local History Guide / GitHub Copilot CLI
GitHub Copilot CLI Local History: Where Copilot Stores Sessions on macOS
GitHub Copilot CLI writes every session to a JSONL event log in your home directory. That file is the only place the prompts, tool calls, shell output, and errors of a finished session survive. Copilot's own SQLite store keeps just the user message and the final answer per turn. This guide covers the exact paths, the event shape, how to read it with jq, and where hand-reading stops being practical.
Agent Sessions reads Copilot's local event logs in read-only mode. It does not upload Copilot transcripts and does not write into Copilot's state.
Where GitHub Copilot CLI Stores Local Data
Current Copilot CLI builds give every session its own directory and write the transcript inside it:
~/.copilot/session-state/<session-id>/events.jsonl
Copilot CLI 1.0.11 introduced that layout. Before it, sessions were flat files in the same folder:
~/.copilot/session-state/<session-id>.jsonl
The session ID is a UUID, and it is the same ID copilot --resume takes. Agent Sessions reads both layouts, so an install that predates the change still shows its old sessions.
The per-session directory holds more than the transcript. Alongside events.jsonl you will typically find workspace.yaml, session.db, and the checkpoints/, files/, research/, and rewind-file-snapshots/ folders. workspace.yaml is the useful one for orientation, because it carries the session's cwd, git_root, repository, and branch:
grep -H '^cwd:' ~/.copilot/session-state/*/workspace.yaml
One directory per session also means a directory can exist without a transcript. On a machine we checked, 20 of 32 session folders held only workspace.yaml and their sidecar directories: no events.jsonl, and no prompt recorded in workspace.yaml either. A folder in that state has nothing to read, and Agent Sessions lists a session only where events.jsonl exists.
Copilot's Own SQLite Store Is Not the Transcript
Copilot keeps a separate database at the config root:
~/.copilot/session-store.db
Here is what it holds. The sessions table has id, cwd, repository, host_type, branch, and summary. The turns table has one row per turn with user_message and assistant_response. There is an FTS5 table, search_index, but on inspection it indexes only turn rows: the same user-message-plus-answer pair.
So the database is a fast index over the conversation, and nothing else. Tool calls, tool arguments, command output, errors, reasoning, model switches, and truncation events exist only in events.jsonl. If you are looking for the command that broke the build, the database will not have it. Agent Sessions reads the event log for the same reason.
What a Copilot Event Looks Like
Each line of events.jsonl is one JSON object with a fixed envelope and a type-specific payload:
{ "type": ..., "data": { ... }, "id": ..., "parentId": ..., "timestamp": ... }
Timestamps are ISO 8601, usually with fractional seconds. The types you will actually meet in a real log:
session.start: carriessessionId,copilotVersion,selectedModel, and acontextobject. This is where the format version is stamped.user.message:contentis the prompt as typed;attachments[]lists files by absolute path.assistant.message:contentis the visible answer, andtoolRequests[]carries the tool calls the model asked for, each with atoolCallId,name, andarguments.tool.execution_start/tool.execution_complete: the actual run. The result lands indata.result.content, with asuccessboolean.assistant.turn_start/assistant.turn_end,assistant.reasoning,system.message,system.notification,hook.start/hook.end.subagent.started/subagent.completed: subagent work is joined to its parent throughparentToolCallId.session.model_change,session.info,session.error,session.resume,session.truncation,session.shutdown, and the router/usage telemetry pairsession.auto_mode_resolvedandsession.usage_checkpoint.
Reading It Yourself
The format is friendly to command-line tools. Newest session first:
ls -t ~/.copilot/session-state/*/events.jsonl | head -1
Every prompt you have ever typed at Copilot CLI:
jq -r 'select(.type=="user.message") | .data.content' \
~/.copilot/session-state/*/events.jsonl
Every command Copilot actually ran, with its arguments:
jq -r 'select(.type=="tool.execution_start")
| "\(.data.toolName)\t\(.data.arguments|tostring)"' \
~/.copilot/session-state/*/events.jsonl
Which session mentioned a thing, since one file is one session:
grep -l "the-thing-you-remember" ~/.copilot/session-state/*/events.jsonl
That last one is the reason the layout is pleasant to work with: grep -l answers "which session was that" directly, and the directory name is the resume ID.
Where Hand-Reading Runs Out
Four things get tedious fast, and they are the honest case for a reader.
- Tool results are not next to their tool calls. The name and arguments arrive on
assistant.message.toolRequests[]ortool.execution_start; the output arrives later ontool.execution_complete. They are joined bytoolCallId, which means reconstructing "what did this command print" is a join, not a grep. - Some of the assistant payload is opaque.
assistant.messagecan carryencryptedContent(several kilobytes) andreasoningOpaquebeside the plaintextcontent. Those blobs are for the provider, not for you. WhenreasoningTextis present it is plaintext and readable; when it is not, the reasoning is in a blob nobody local can open. - Escapes. Tool output arrives with literal
\nand\tsequences inside the JSON string, so raw output looks like one long line until something un-escapes it. - The transcript does not know what project it is. The working directory is in
workspace.yamland insession.start'scontext, not on the events. Cross-referencing sessions to repositories is a second pass over a second file.
Copilot also truncates long conversations itself. A session.truncation event records how many messages and tokens were dropped from the model's context. Those messages stay in the event log, but the model stopped seeing them, which explains a session whose later answers look forgetful.
What Agent Sessions Does
- Lists Copilot CLI sessions beside Codex, Claude Code, Cursor, OpenCode, Hermes, OpenClaw, and the other supported agents in one list.
- Full-text searches prompts, assistant text, tool names, tool arguments, command output, and error text across every session at once.
- Joins
tool.execution_startandtool.execution_completebytoolCallIdso a tool call and its output read as one block, with failures marked as errors rather than results. - Un-escapes tool output and renders the transcript as a timeline instead of raw JSONL.
- Extracts image attachments referenced by path from
user.messageinto the Image Browser. - Reads both the current
<session-id>/events.jsonllayout and the legacy flat<session-id>.jsonlfiles. - Builds resume commands (
copilot --resume=<session-id>, orcopilot --continuefor the most recent) and can launch them in Terminal.app, iTerm2, or Warp. - Lets you point it at a different sessions root in Preferences if your Copilot state does not live under
~/.copilot.
Copilot CLI session-format support has been verified through Copilot CLI 1.0.80, and the format is re-checked weekly against fresh sessions.
What This Does Not Do
- Decrypt
encryptedContentorreasoningOpaque. Those are opaque blobs; no local tool opens them. - Recover a session that has no
events.jsonl, or context Copilot dropped during truncation. - Read
~/.copilot/session-store.db. Agent Sessions reads the event log, which is the fuller record. - Write into Copilot's state, or replace Copilot's own session picker.
- Upload Copilot transcripts to a hosted search service.
Related Guides
- Antigravity CLI local history
- Claude Code JSONL history
- Codex local history
- Cursor Agent local history