GitHub Copilot CLI logs: locations and VS Code history
GitHub Copilot CLI saves session history at ~/.copilot/session-state/<session-id>/events.jsonl and diagnostic logs in ~/.copilot/logs/. Copilot Chat in VS Code saves chat history under User/workspaceStorage/<hash>/chatSessions/ in the editor's user-data directory. The table below gives the locations; the rest of this guide explains which saved token counters you can count.
Where are GitHub Copilot logs stored?
| What you need | Default location |
|---|---|
| Copilot CLI session history and usage | ~/.copilot/session-state/<session-id>/events.jsonlOn Windows: %USERPROFILE%\.copilot\session-state\<session-id>\events.jsonl |
| Copilot CLI diagnostic logs | ~/.copilot/logs/Enter /session in Copilot CLI to find the current log file. |
| VS Code chat history on macOS | ~/Library/Application Support/Code/User/workspaceStorage/<hash>/chatSessions/ |
| VS Code chat history on Windows | %APPDATA%\Code\User\workspaceStorage\<hash>\chatSessions\ |
| VS Code chat history on Linux | ~/.config/Code/User/workspaceStorage/<hash>/chatSessions/ |
COPILOT_HOME overrides the CLI root. VS Code profiles, Insiders, portable installs and --user-data-dir can change the editor paths; see VS Code locations below. These files can contain conversations and tool output, so inspect them locally before sharing anything.
For connection errors in VS Code, open View → Output and select GitHub Copilot, following GitHub's diagnostic-log instructions. Saved chat sessions are the source for the usage accounting in this guide.
What is in a Copilot CLI session log?
GitHub's configuration-directory reference puts everything Copilot CLI keeps under ~/.copilot, and COPILOT_HOME replaces that whole path when it is set. session-state/ holds one directory per session id. Each one has events.jsonl, the event log the CLI replays to resume a session, beside workspace artifacts such as plans, checkpoints and tracked files. Deleting a session directory removes the local copy only; a session synced to a GitHub account is stored separately.
The event log is one JSON record per line: a type, a timestamp, an id and a data object. The prompts, the replies, every tool's arguments and output, and the paths of edited files are all in there. So are the counters. The usage a collector can rely on is written at session.shutdown, as data.modelMetrics keyed by model name, with usage.inputTokens, usage.outputTokens, usage.cacheReadTokens, usage.cacheWriteTokens, usage.reasoningTokens and requests.count.
| Event | What a counter reads | What it leaves alone |
|---|---|---|
session.startsession.resumesession.context_changed | The session id and the working directory, reduced to a repository name on the machine | The rest of the context |
session.shutdown | modelMetrics per model: token counters and request count; the cumulative lines added and removed | Summaries and the final message |
skill.invoked | The skill's name, whether the person or the model invoked it, and the size of the injected text | The injected text itself |
tool.execution_starttool.execution_complete | The tool name, the MCP server name, success or failure, elapsed time | Arguments and output |
user.messageassistant.turn_startassistant.turn_endhook.starthook.endsession.compaction_complete | That the day was active; turn, hook and compaction counts; durations | Message text and hook output |
How do you count a Copilot CLI session correctly?
- The shutdown counters are cumulative. A resumed session appends another
session.shutdownto the same file, and each one carries the running total since the session began. Diff consecutive checkpoints per model and keep the increase; a repeated event id, or a total lower than the previous one, adds nothing. - Input includes the cache. Copilot's
inputTokenscounts cache reads and cache writes inside it. SubtractcacheReadTokensandcacheWriteTokensto get fresh input, then price the three separately. Pricing all of it at the fresh rate overstates a long session several times over. - Reasoning is part of output.
reasoningTokensis a subset ofoutputTokens, not an addition to it. Adding it on top inflates every reasoning-heavy model. - The day is the shutdown's day, in UTC. The checkpoint is the only durable record, so the increase lands on the day the session shut down, even when the work started the evening before. There is no per-request timeline to spread it over.
- No shutdown, no total. A session still open, or one that crashed, has written no checkpoint. Its tokens are unknown, not zero; the row appears when the session ends.
- Sub-agent snapshots are already inside the parent. Records stamped with an
agentIdbelong to a sub-agent whose totals the main shutdown already includes. Counting them again counts the same tokens twice.
Beside the tokens, the same file names the capabilities that fired: skill.invoked carries the skill's name and whether the person or the model chose it, and tool.execution_start carries the MCP server name separately from the arguments. Copilot CLI does not durably attribute later tokens to a skill, and its ordinary slash-command events are not written to the log, so a skill's cost share and a command's invocation count are not available from these files. Copilot skills themselves live in the personal ~/.copilot and ~/.agents directories, a project's .github, .agents and .claude, and installed plugins, which is where an inventory looks.
Where does VS Code save Copilot Chat history?
VS Code keeps chat sessions inside its user-data directory: ~/Library/Application Support/Code/User on macOS, %APPDATA%\Code\User on Windows, and ~/.config/Code/User on Linux (under $XDG_CONFIG_HOME when that is set), with Code - Insiders beside Code for the Insiders build. A portable installation puts the User/ directory under $VSCODE_PORTABLE/user-data. The --user-data-dir option selects a different user-data root.
Location under User/ | What is there |
|---|---|
workspaceStorage/<hash>/chatSessions/ | One file per session opened in that workspace; workspace.json beside it names the folder |
globalStorage/emptyWindowChatSessions/ | Sessions started in a window with no folder open |
globalStorage/transferredChatSessions/ | Sessions moved between windows |
profiles/<id>/globalStorage/ | The same two folders for each additional profile |
Each session file is named by its session id with a .json or .jsonl extension. The .json form is a full snapshot that older builds rewrote on every change. Current builds keep a .jsonl mutation log instead: the first line is the initial state, and every later line is one operation on it, a set, an array splice or append, or a delete, keyed by the path it changes (the format is objectMutationLog.ts in VS Code's source). When both files exist for one session, the .jsonl is the live copy and the .json a migrated leftover, so a reader that counts both doubles the session.
Inside the replayed state, each entry of requests[] is one turn. The fields a counter needs:
- The model.
modelId, andresult.metadata.resolvedModelwhen Auto picked the model, without thecopilot/prefix. - Whole-turn totals.
modelTotals[], one entry per model withinputTokens,outputTokensandcachedTokens, across every tool-call round of the turn. Use these when they are present;inputTokensincludescachedTokens, so subtract. - The last call only, on older records.
promptTokensandcompletionTokensdescribe the last model call of the request, not the sum of its rounds.result.metadata.toolCallRoundssays how many rounds there were; when that is not exactly one, the record is incomplete. Do not multiply the last call by the number of rounds, because the calls differ in size; report the turn as partial. - The day and the duration.
timestampandelapsedMs. - Names. The slash command on the turn, tool call ids and names in the rounds, and the server label on MCP tool results.
Neither form records a cache write, and the older records carry no cached split at all, so a legacy request is priced with its recorded input at the input rate. The estimate can only be as complete as what the editor saved, which is why a collector should carry a flag for the measurements a record did not have rather than write a zero. workspace.json beside a workspace's sessions names its folder, and a session's own workingDirectory names it too; either becomes a repository name on the machine. A multi-root workspace has no single folder and stays unattributed.
Some usage is not in these files at all. Inline completions and next edit suggestions are not chat requests, leave no counters, and GitHub does not bill them. The cloud coding agent and Copilot code review run on GitHub's side. A session in a remote workspace belongs to the remote machine.
Why are my Copilot logs or token totals missing?
- The CLI directory moved. Check whether
COPILOT_HOMEis set in the environment that runs Copilot. Itssession-state/directory holds the history;logs/holds diagnostics. - The CLI session has no shutdown checkpoint. An open or interrupted session may have an event log without complete token totals. Missing counters do not establish zero usage.
- VS Code used a different store. Check Insiders, a named profile, portable mode or the directory passed to
--user-data-dir. Chats without an open folder useemptyWindowChatSessions/, as listed above. - Kibble cannot find a custom VS Code directory. Set
KIBBLE_VSCODE_USER_DATA_DIRto the same root you pass to--user-data-dir, then runkibble push --dry-run. Use the root containingUser/, not the workspace folder. - The activity was outside saved local chat. Inline completions, remote-only sessions and GitHub's cloud agent do not become local chat token totals. Older multi-round VS Code records can also be incomplete.
With Kibble installed, kibble doctor checks local collection setup and kibble push --dry-run previews counts without uploading them. The collector's coverage notes describe these limits.
What is a Copilot token worth?
Since 1 June 2026 GitHub bills Copilot in AI credits consumed by tokens at a published rate per model, one credit being one US cent. Four rates from GitHub's models and pricing page, read on 10 September 2026, in US dollars per million tokens for the default context tier:
| Model | Input | Cached input | Cache write | Output |
|---|---|---|---|---|
| GPT-5.6 Luna (the default) | $0.20 | $0.02 | $0.25 | $1.20 |
| GPT-5.4 | $2.50 | $0.25 | not applicable | $15.00 |
| Claude Sonnet 5 | $2.00 | $0.20 | $2.50 | $10.00 |
| Claude Opus 5 | $5.00 | $0.50 | $6.25 | $25.00 |
Price each counter at its own rate and add. A session whose input is mostly cache reads costs less than pricing every input token at the fresh rate; the difference depends on the cache share and the model's rates. This is the same lesson as Claude Code's four token fields. Earlier OpenAI models have no cache-write charge, so their cache-write tokens are ordinary input.
A local estimate at those rates and GitHub's bill are two different numbers. GitHub's own per-user figure, the ai_credits_used field its usage metrics API has reported since 19 June 2026, is described by GitHub as a metrics signal rather than a billed total, and it has no split by model, surface or repository. Kibble's Copilot rows are estimates priced from the same pinned rate table its Claude Code and Codex rows use, kept as integer micro-dollars, and never added to a bill. What the scenarios cost in credits works three examples through.
How do the CLI and the editor add up?
A person who used Copilot CLI and Copilot Chat on the same day with the same model produces two sets of counters for one day-and-model row. A collector must add them before it sends anything, because a per-day upsert replaces a row it has already seen: two collectors pushing the same grain would leave whichever arrived last. Kibble sums the CLI and editor totals into one copilot row per day and model on the machine, then pushes once.
What leaves the machine
These are the Copilot rows of Kibble's field list; the privacy policy is the authoritative version and the server rejects anything not on it.
| Sent | Never sent |
|---|---|
The day, the agent (copilot), the model name | Prompts, replies, tool output |
| Fresh input, output, cache read, cache write, reasoning; request count | Tool arguments; the text a skill injected |
| Opaque session ids | Paths of edited files; plan and checkpoint artifacts |
| The repository's name | The working directory itself, the username in it, the hostname |
| Skill, command and MCP server names with counts, when the organization asks | The GitHub login, tokens and user ids in config.json |
| How Copilot is billed: GitHub login, API provider or cloud provider | The tier or seat price, which the machine cannot tell |
The counters and the conversation are in the same file. A collector's privacy is the part of its code that reads one and not the other.
How do I collect Copilot usage across a team?
Install Kibble on each participating machine and connect it with kibble login. The first sync imports today and the previous 29 UTC days where supported logs exist. With automatic collection enabled, login attempts the first upload and schedules later pushes; otherwise run kibble push manually. The team setup guide covers organization setup, addressed invitations and checking coverage.
The open-source adapters read the supported CLI and VS Code counters into one dashboard alongside your other coding agents. Missing counters remain unavailable, and the cost figures are estimates. Prompts, replies and tool output stay on the machine.
Start with one engineer's available logs, check the totals, then invite the rest of the team.
Set up team usage tracking