claude-code¶
Claude Code (catalog agent claude-code) is Anthropic's closed-source coding agent CLI; Claude
Desktop's Code tab launches the same CLI (claude-desktop.md), and the VS Code
extension runs it too and writes into the same ~/.claude tree, so the extension is a surface of
this agent and not a catalog agent of its own (VS Code extension files).
It writes one append-only JSONL transcript per session, a tree of records linked by
uuid/parentUuid, with each subagent in its own JSONL file under the session directory, plus a
global prompt history. On 2.1.309 a session can be handed to the session daemon, a background
claude supervisor process that keeps sessions running after the console that started them exits,
and run by one of its workers, the claude processes the daemon starts to hold those sessions
(Session daemon files). MCP (Model Context Protocol) servers are the tool
servers the CLI starts or connects to; a connector is an MCP server attached to the claude.ai
account rather than configured on the host. A slug is a path or name with every character other
than a letter or digit replaced by -, the scheme of the projects/ keys. No other agent in the
catalog shares this format; Claude Desktop's Code sessions point back at the same projects/ files
(Where the project path is recorded). The agent is parsed by
the claude-code parser (Parser plan).
Source and evidence¶
Closed source. The public repository
anthropics/claude-code, at
1132b8a
(2026-10-21), is published under Anthropic's Commercial Terms of Service and ships the README,
the changelog, example plugins, the TypeScript of four built-in mods (sec-default, diff,
telemetry, agents-md) and the plugin API's type declarations, not the CLI, so the claims
about the CLI below have no commit links. No binary below was ever
executed. Each claim names its evidence level, best first:
- Bundle 2.1.288, the evidence for the Collection part (Per-user storage to
Where the project path is recorded):
npm pack @anthropic-ai/claude-codegives 2.1.288, a 28 KB shim whosepackage.jsonlistsoptionalDependencieson eight platform packages (scratchpad/npm/cc/package/package.json,scratchpad/being the working directory of the research session that read the package).npm pack @anthropic-ai/claude-code-linux-x64@2.1.288gives a single 245 MB Bun-compiled binaryclaude(Bun is the JavaScript runtime that packs the CLI's code and itself into one executable);strings -n 6of it isscratchpad/npm/cc.strings, and every "cc:NNN" in the Collection part is a line in that file. Evidence level per claim there: bundle unless marked docs or local. - Bundle 2.1.289, the evidence for the Parsing part (Transcript stores to
Parser plan):
npm pack @anthropic-ai/claude-code@2.1.289(shim, sha256d72b0a94b84e51fbe5686577f322488a89f844c275985e8054e16951c6dd3e64) andnpm pack @anthropic-ai/claude-code-linux-x64@2.1.289(sha25650a6eb433422febcfa7b7ac1e78ba795cdf79f1fe58f017fb1d9aeb9491b337d), whose single Bun-compiled binarypackage/claude(246,107,320 bytes, sha256a186b99e4a9c88366cd49df2f7dad56c61fc306ef0140b19ee64b7c42a8d1348) was read withstrings -n 6. Everycc:NNNin the Parsing part is a 1-based line in that strings file (531,386 lines); the minified JavaScript keeps record type literals and object shapes. 2.1.289 was chosen because it is the newestversionin the local transcripts. The two strings files are numbered differently, so acc:line in the Collection part does not match one in the Parsing part. - Docs: Claude Code settings,
Claude Code IAM,
Claude Code managed settings;
Claude Desktop data storage;
for the retention sweep and the session scratchpad (the per-session temporary directory Claude
Code gives the model, Scratch directories),
Explore the .claude directory,
Settings reference,
cleanupPeriodDaysand Monitoring usage, Retention sweep event (read 2026-10-26); for a Remote Control server's sessions after it stops, Resume sessions after stopping the server (read 2026-10-26). - Local: the author's host (Observed). A shape the Parsing part marks observed was seen on the WSL2 install; one marked bundle rests on the binary only.
- Windows 2.1.309: the clean-VM round of 2026-10-23 (Observed). A claim marked "on Windows 2.1.309" rests on that round's listings and the shape tool's key names, types and enum values; no value was read.
- Linux 2.1.310: the clean-VM round of 2026-10-25 and 2026-10-26 (Observed). A claim marked "on Linux 2.1.310" rests on that round's listings, file modes and the shape tool's key names, types and enum values, in the same way.
- macOS 2.1.310: the clean-VM round of 2026-10-26 (Observed). A claim marked "on macOS 2.1.310" rests on that round's listings, file modes, the names of the keychain items whose service or label mentions Claude (never their secrets) and the shape tool's key names, types and enum values, in the same way.
- Vendor text in the bundle: the built-in "Fewer Permission Prompts" prompt describes the
transcript layout for the model itself (2.1.289 strings file, cc:489602: "Session transcripts
live at
~/.claude/projects/<sanitized-cwd>/*.jsonl. Each line is a JSON object. Tool calls appear asassistantmessages withmessage.content[]entries oftype: "tool_use""). - Binary 2.1.296 (issue 93): the
author's installed CLI,
~/.local/share/claude/versions/2.1.296, a Bun-compiled Mach-O arm64 executable of 240,664,432 bytes, sha256c9b5341637becbd423ddffc5b254afb645682a3868cb708bbc6cc0e7bb419937, searched for byte strings. Citations areclaude 2.1.296 darwin-arm64 @<byte offset>with the literal snippet, as for the 2.1.295 Linux build below; the offsets are only valid for this build. - Binary 2.1.295: the Linux arm64 build of CLI 2.1.295 that Claude Desktop downloads
(
https://downloads.claude.ai/claude-code-releases/2.1.295/linux-arm64/claude.zst, decompressed 255,516,664 bytes, sha256cfb9dc1332fb6f92a683f835823e91b3179a803398ca7852cd2381a3cb1dea3b); citations are writtenclaude 2.1.295 linux-arm64 @<byte offset>with a literal snippet, because thecc:line numbers belong to 2.1.288 and 2.1.289. It is the evidence for the CLI cache directory and Remote Control.
Observed¶
- Windows 11 arm64 (build 10.0.26300), CLI 2.1.309 win32-arm64 from the native installer, a clean
virtual machine (VM), 2026-10-23 (issue 256), one listing
diff per step, file names, sizes, key names and enum values only: the install
(Native installer); the first launch, browser sign-in and trust dialog
(Credentials,
.claude.json, Where the project path is recorded); an/exitwith no prompt and a question the model answered with a shell call inautomode (Transcript stores, Record schema); a reboot with that session open, after which the next start's session was handed to the session daemon (Session daemon files, Session daemon records); the Shift+Tab mode cycle, a one-time approval, a denial with a typed reason and an allow-always grant (Permission decisions, Permission configuration); a file edit (File-history records); accept-edits and plan mode, with the plan file (Files that hold evidence); a/modelswitch saved as the default (Joins and timestamps); a subagent, a large output persisted to a file, a pasted text, a prompt typed while a turn ran,/compact,/renameand/exit(Record schema,history.jsonl);claude --resume, a refusedclaude --continueandclaude agents(Joins and timestamps); a local-scope MCP server (CLI cache directory, MCP state);/install-github-appand a read-only pull-request question (Source-host integration); Remote Control from the Claude iPhone app (Remote Control); a cloud session (a Claude Code session that runs on Anthropic's servers against a GitHub repository, Cloud sessions), which the CLI did not offer this account;/loopand/schedule(Scheduled tasks); the VS Code extension (VS Code extension files, VS Code extension sessions); then/clear, acleanupPeriodDayssetting, the project directory removed and a reboot (What survives a delete, Scratch directories). - Ubuntu 26.04.1 LTS arm64, CLI 2.1.310 linux-arm64 from the native installer, a clean VM,
2026-10-25 and 2026-10-26 (issue 256),
one listing diff per step, file names, sizes, modes, key names and enum values only: the install
(Native installer); the first launch, browser sign-in and trust dialog
(Credentials,
.claude.json, CLI cache directory, Scratch directories, Where the project path is recorded); an/exitwith no prompt, then a question answered with no tool call and one answered with aReadinautomode (Transcript stores, Record schema); the Shift+Tab mode cycle, two one-time approvals, a denial with a typed reason and an allow-always grant (Permission decisions, Permission configuration); a file edit and a prompt on a detached head (File-history records, Joins and timestamps); accept-edits and plan mode, once with a question and once with a task that wrote a plan file (Files that hold evidence); a/modelswitch (Joins and timestamps); a subagent, a large output persisted to a file, a short and a long paste, a prompt typed while a turn ran,/compact,/renameand/exit(Record schema,history.jsonl);claude --resume,claude --continueandclaude agents(Joins and timestamps, Session daemon files); a local-scope MCP server (MCP state);gh auth login,/install-github-appand a read-only pull-request question (Source-host integration); Remote Control from the Claude phone app (Remote Control); a cloud session, which the CLI did not offer this account;/loopand/schedule(Scheduled tasks); the VS Code extension (VS Code extension files, VS Code extension sessions);/clear, acleanupPeriodDayssetting with a backdated transcript, short and idle sessions and a--debugsession left open overnight, the project directory removed and a reboot (What survives a delete, Scratch directories, Files that hold evidence). - macOS 26.6.2 arm64, CLI 2.1.310 darwin-arm64 from the native installer, a clean VM, 2026-10-26
(issue 256), one listing diff per step,
file names, sizes, modes, key names, keychain item names and enum values only; every step ran
over ssh inside
screen(a terminal multiplexer that keeps the console running between ssh commands), except the browser sign-ins, a session started from Terminal.app in the desktop session and the prompt sent from claude.ai, which a person made by hand: the install (Native installer); the first launch, browser sign-in and trust dialog (Credentials,.claude.json, CLI cache directory, Scratch directories, Where the project path is recorded); an/exitwith no prompt, which registered a URL handler (Files that hold evidence); a question answered with aReadinautomode and one answered with no tool call (Transcript stores, Record schema); the Shift+Tab mode cycle, a one-time approval, two denials, the second through the dialog's amend option, and an allow-always grant (Permission decisions, Permission configuration); a file edit and a prompt on a detached head (File-history records, Joins and timestamps); accept-edits and plan mode with a task that wrote a plan file (Files that hold evidence); a/modelswitch (Joins and timestamps); a subagent, a large output persisted to a file and then written to the session scratchpad, a short and a long paste, a command the model ran in the background, a prompt typed while an answer streamed,/compact,/renameand/exit(Record schema,history.jsonl, Scratch directories);claude --resumeandclaude --continue(Joins and timestamps); a local-scope MCP server (MCP state);/install-github-appbefore and aftergh auth login, the login made once over ssh and once from Terminal.app, and a read-only pull-request question (Source-host integration);claude remote-controldriven from claude.ai (Remote Control); a cloud session created with--cloudand brought into a local session with--teleport(Cloud sessions);/loop(Scheduled tasks); the VS Code extension (VS Code extension files, VS Code extension sessions); then/clear, acleanupPeriodDaysof 0,/logoutand a sign-in from Terminal.app, the project directory removed and a reboot (What survives a delete, Credentials, Scratch directories). - macOS, CLI 2.1.296, the author's install, 2026-10-13
(issue 93), key names, enum values and
counts only: 308
permission-moderecords across four session files, every onepermissionModeauto(Record schema);permissionModeon everyuserprompt line;message.modelandrequestedModelon everyassistantline, equal on every line seen; a/modelswitch written as auserline<command-name>/model</command-name>followed by auserline holding the command output (Joins and timestamps); summary lines withmodelUsage.<model id>.{inputTokens, outputTokens, ...}. The permission configuration keys of Permission configuration were seen in~/.claude.jsonand the settings files of the same install, by name only. - macOS, CLI 2.1.295 launched by the Claude Desktop Code tab, 2026-10-11: the CLI cache directory
~/Library/Caches/claude-cli-nodejs/(CLI cache directory) with itsclaude-cli-nodejsMCP logs (Transcript stores), andsessions/<pid>.<sha256>.key(Credentials) beside thesessions/<pid>.jsonregistry (Remote Control); in the transcriptsrequestedModel, thestop_hook_summarykeys andrenderedBesideToolResult(Record schema). - Linux, the Ubuntu lab install of 2026-10-11, CLI 2.1.295 launched from the Claude Desktop Code
tab:
sessions/<pid>.<sha256>.keybesidesessions/<pid>.json(Credentials), and the registry's key names and value types only (Remote Control). - Linux (WSL2), the author's host, October 2026, versions 2.1.286 to 2.1.289 (the native install was
2.1.288 when the layout was checked): the layout was confirmed names-only against
~/.claude, and claims marked local rest on it. 95 transcript files (15 session files, 80 subagent files), 31,116 lines, 0 unparseable, plus 80 subagent.meta.jsonfiles, 37 persisted tool outputs,history.jsonl(162 lines) andsessions/*.json(3), read by key path and value type only (Record schema to Other stores).
Source reviews¶
- 2026-10-26: three of Anthropic's Claude Code documentation pages,
Explore the .claude directory,
Settings reference,
cleanupPeriodDaysand Monitoring usage, Retention sweep event: What survives a delete now states what the retention sweep removes and keeps and namesclaude purge, and Scratch directories the conditions under which a session gets a scratchpad. - 2026-10-21: anthropics/claude-code at
1132b8a:
the public repository was checked for the CLI's source and does not ship it; the opening of
Source and evidence says what it holds, and
Project-local files gains the
AGENTS.mdinstruction files theagents-mdmod reads. - 2026-10-19: the agents that read, drive, copy or index Claude Code state, at Crush 7f799fb, Agent Zero ff2a5b9, Hermes Agent 86fa9e6, Cline 41e75ef, Pydantic AI 0ba01f9 and Zed 9cd14da. Added Used by other agents and the copies and the Zed index under What survives a delete.
Collection¶
Per-user storage¶
Config home = process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), NFC-normalised
(cc:472945 o()??u(R(),".claude"), cc:497291). Same on Linux, macOS and Windows
(%USERPROFILE%\.claude, docs settings page; observed on Windows 2.1.309). No Library or AppData
paths for user state, apart from the cache directory below and, on Windows, the temp root
(Scratch directories); the native installer's ~/.local and ~/.cache
directories are used on Windows as well (Native installer), and Claude Code
appends to the user's global git excludes file ~/.config/git/ignore, creating it when absent
(Files that hold evidence). CLAUDE_CONFIG_DIR relocates everything
including
.credentials.json and the keychain entry name (docs /iam "Credential management"); separate
account dirs such as ~/.claude-work are documented practice (docs /iam "multiple accounts").
Known top-level directories inside the config home, from the literal set at cc:482244:
agent-memory, agent-memory-local, agent-memory-project, api-dumps, backups, cache,
chrome, cowork_plugins, daemon, debug, downloads, dump-prompts, feedback,
feedback-bundles, file-history, file-transfers, ide, image-cache, jobs, local,
local-settings, logs, mcp-discovery-cache, mcp-skill-archives, paste-cache, plans,
plugins, project-settings, projects, remote, scratch, seed-admin, session-env,
sessions, shares, shell-snapshots, startup-perf, state, statsig, storage-v2,
systemd, tasks, teams, telemetry, todos, traces, uploads, usage-data. User-config
subdirs also include commands, agents, output-styles, skills, workflows, routines,
themes, rules (cc:473782). Known top-level files (cc:483303): .claude.json,
.claude.json.backup, .credentials.json, history.jsonl, .session_ingress_token,
policy-limits.json, remote-settings.json, hfi-auth.json, stats-cache.json,
mcp-needs-auth-cache.json, gh-pr-status-cache.json, antproto.json, active-time.json,
loop.md, server-sessions.json, computer-use.lock, server.lock; plus settings.json,
settings.local.json (cc:436317-8), .device-keys.json and .config.json (cc:477075). On Windows
2.1.309 the round saw backups, cache, daemon, downloads, file-history, ide, jobs,
paste-cache, plans, plugins, projects, session-env, sessions, shell-snapshots,
skills and state written, and the files .credentials.json, history.jsonl, settings.json,
.last-cleanup, daemon.lock, daemon.log and daemon.status.json; debug, statsig, todos
and .device-keys.json were never written. On Linux 2.1.310 the round saw the same directories,
plus debug (written only by a claude --debug session), and the same files except daemon.lock
and daemon.status.json; statsig, todos, startup-perf and .device-keys.json were never
written. sessions/, daemon/ and ide/ are mode 0700 there, and the files holding keys or
tokens 0600 (Credentials). On macOS 2.1.310 the round saw the Linux set less
daemon, jobs and debug (no step started the session daemon or a --debug session), plus
bridge-spawn while a Remote Control server ran (Remote Control) and
seed-admin, created empty by claude --cloud (Cloud sessions);
.credentials.json only while a session started over ssh held the sign-in
(Credentials); and, outside the config home, a URL handler bundle under
~/Applications (Files that hold evidence) and the socket directory
/tmp/cc-socks (Scratch directories). statsig, todos, startup-perf
and .device-keys.json were never written there either.
Scratch directories¶
On Windows the temp root is under the home: %LOCALAPPDATA%\Temp\claude\
(AppData/Local/Temp/claude), created empty at the first launch, not the claude-<uid> form of
Linux. Observed on Windows 2.1.309, each session id gets <slug>\<session id>\scratchpad\ and
<slug>\<session id>\tasks\, <slug> being the project key of
Transcript stores (C--Users-sean-lab-proj for the lab project,
C--Users-sean for the home). A directory appears for every session id the CLI starts, including
ids that never get a transcript: a start refused by a settings error, a probe session the VS Code
extension opens beside its own, and the replacement workers the session daemon starts at each
/exit (Session daemon files). tasks\ is filled at the session's first
tool run: tasks\<agent id>.output (0 B) for a subagent, tasks\<9 base-36 chars>.output holding
the output of a command Claude Code moved to the background (22 B), and short-lived task files
created and removed during a long command. The temp root survives a reboot (Windows does not clear
%TEMP% at boot). The housekeeping sweep that writes .last-cleanup removed the session
directories of earlier sessions, an open session's included, while the <slug> directories stayed;
the tree of a session whose process died in a reboot was gone after the next start. The catalog
collects it since this round (claude-code|AppData/Local/Temp/claude). The session's scratchpad is
also named by the environment attachment's snapshot.scratchpadDirectory key
(attachment records).
The vendor's
Explore the .claude directory
names the session scratchpad, a per-session working directory Claude Code gives the model for
temporary files: /tmp/claude-<uid>/<project>/<session-id>/scratchpad/ on Linux (under $TMPDIR
when it is set), /private/tmp/claude-<uid>/... on macOS and %TEMP%\claude\... on Windows,
movable with CLAUDE_CODE_TMPDIR; a session gets one only when it is signed in with a claude.ai
account, on the Anthropic API, with enableArtifact not set to false. The page says the
scratchpad lasts as long as the session's transcript, the retention sweep deleting the directory
with the transcript, and that claude purge does not touch the temp directory
(What survives a delete).
Observed on Linux 2.1.310 (2026-10-25 and 2026-10-26), the temp root is /tmp/claude-<uid>
(/tmp/claude-1000/), outside the home, and holds one directory per process, not per session:
<slug>/<uuid>/scratchpad/, every level mode 0700, created empty at the process start. A new
session's directory is named by its session id; a claude --resume, a claude --continue, the
claude agents console and its supervisor, and even claude mcp add, which runs no session, each
got a directory of their own under a uuid that is not the resumed session's, so by the end of the
round the temp root held sixteen such directories under two slugs, the project's and -home-sean
from one start in the home. <slug>/<uuid>/tasks/ (mode 0775) appears at the session's first shell
call:
tasks/<agent id>.output is a symlink to the subagent's transcript,
projects/<slug>/<session id>/subagents/agent-<agent id>.jsonl (on Windows a 0 B file), and
tasks/<9 base-36 chars>.output (22 B, mode 0664) holds the output of a command Claude Code moved
to the background. Beside the slugs, bash-edit-diff/<19 digits>-<19 digits>-<16 hex>/ (mode 0700)
is a snapshot of the project's working tree taken at a shell call, so that Claude Code can diff
what the command changed (the bash_edit_diff feature, behind the tengu_agile_glade flag): a
non-bare git repository with HEAD, refs/heads/main, a config of four keys
(core.repositoryformatversion, core.bare, core.logallrefupdates, core.untrackedcache), an
empty config.global (a GIT_CONFIG_GLOBAL override), an index (659 B, two entries),
info/attributes, info/exclude, loose objects and objects/info/alternates, git's list of other
object directories a repository borrows from, which names the project's .git/objects. Its one
commit, holding .gitignore, README.md and calc.py, is its own and not the project's HEAD;
nothing under the project's .git changed. A second, empty <ids>/ directory sat beside it. Both
were removed at the session's exit, the empty bash-edit-diff/ parent staying, so the snapshots are
on disk only for a live session or one whose process died. Six seconds after the first launch the
Bun runtime of the binary also wrote /tmp/.bun-<uid>-<16 hex>.node (976,328 B, mode 0600), a
native module it extracts to /tmp, which no console process had mapped when it was checked.
The tree outlives /exit. The retention sweep deleted the scratchpad directory of the transcript it
deleted (What survives a delete), so the temp root is bounded by
cleanupPeriodDays like the transcripts. /tmp on Ubuntu 26.04 is tmpfs, a file system held in
memory and emptied at every boot, so the reboot removed the whole temp root, both project slugs,
the tasks/ files and symlink, bash-edit-diff/ and the Bun module, while on Windows the temp root
survived a reboot. A collection taken on Linux after a reboot finds none of it. On macOS 26.6 a
reboot emptied /private/tmp the same way (2.1.310, 2026-10-26: all nineteen process directories
of the project slug, the scratchpad file, the tasks/ outputs and bash-edit-diff/, and the
/tmp/cc-socks/ directory were gone after boot), which the vendor's scratchpad page states as the
operating system clearing the temp location, such as on restart
(Session scratchpad directory). The Claude Desktop
Code tab's use of the same root is in Where the project path is recorded in
claude-desktop.md; on Linux and macOS the
temp root is not collected
(issue 163).
Observed on macOS 2.1.310 (2026-10-26), the temp root is /private/tmp/claude-<uid>
(/tmp/claude-501, /tmp being a link to /private/tmp), the vendor's path, and not under the
per-user $TMPDIR in /var/folders. The first launch created it empty (mode 0700) before the
sign-in, and that first, onboarding session got no directory in it. From the second start on,
every process got <slug>/<uuid>/scratchpad/ (every level 0700) at its start, before any prompt:
under the session id for a new session, and under a uuid of its own for claude --resume (one
found in no transcript, registry or history line), claude --continue, each of claude mcp add
and claude mcp list, claude --cloud, and a Remote Control server and the session it spawned; a
VS Code extension session made two, and /clear added a directory named by the new session id.
The project's slug held 19 such directories by the end. tasks/ (mode 0755) appeared at a
session's first shell call and held <agent id>.output, a symlink to the subagent's
transcript, and <9 base-36 chars>.output (22 B, a regular file), the output of a
command the model ran in the background. scratchpad/words-3000.txt (31,286 B, mode 0644) was
written by a shell redirect the model chose after a 3,000-line output had been persisted, the
first file seen inside a scratchpad on any platform: the scratchpad holds output of the model's
own, which exists nowhere else. bash-edit-diff/ beside the slugs was created empty at the exit
of a session that ran gh, and while the VS Code extension's session ran it held one snapshot
directory with the layout of the Linux one above (HEAD, config, config.global, an index of
661 B, info/attributes, info/exclude a copy of the project's, one loose object,
objects/info/alternates naming the project's .git/objects, and refs/heads/main), removed
when that session exited.
Each claude process on macOS 2.1.310 also creates /tmp/cc-socks/<pid>.sock, a Unix socket
(mode 0600, in a directory of mode 0700) named by its process id, at its start. /exit removes
it, and a process that ends otherwise leaves it: the claude process the native installer ran
left its socket behind. Where the Linux and Windows builds put such a socket was not surveyed.
The reboot cleared /private/tmp, removing the whole temp root with the scratchpad file, the
tasks/ outputs and symlink, and /tmp/cc-socks/, while nothing under ~/.claude changed, so a
collection taken on macOS after a reboot finds none of it, as on Linux. Neither directory is
collected (issue 163).
Files that hold evidence¶
projects/<key>/<sessionId>.jsonl: transcripts, with<key>derived from the cwd (cc:473017, cc:473728);projects/<key>/<sessionId>/subagents/agent-<id>.jsonl: subagent transcripts (cc:476679);projects/<key>/memory/*.md: per-project memory (cc:489631). The naming and the sidecars are in Transcript stores. Local shows-home-sean-dev-...dirs holding<uuid>.jsonl,<uuid>/,memory/; Windows 2.1.309 showsC--Users-sean-lab-projholding<uuid>.jsonl,<uuid>/withsubagents/,tool-results/andcustom-title.json, and an emptymemory/created at the first prompt; Linux 2.1.310 shows the same under-home-sean-lab-proj, and macOS 2.1.310 under-Users-sean-lab-proj, withbridge-pointer.jsonbeside the transcripts once a Remote Control server had run there andccr-tip.jsonin the directory of the session it spawned (Transcript stores, Remote Control).history.jsonl: prompt history, with retention pruning (cc:338013-15, 478233); the vendor page says the retention sweep keeps it (What survives a delete).paste-cache/<16 hex>.txt: the text of a long paste, named by the hashhistory.jsonlrecords (history.jsonl); created at the first such paste (6,604 B on Windows 2.1.309; 15,108 B, mode 0600, on Linux 2.1.310; 2,415 B, mode 0600 in a directory of mode 0755, on macOS 2.1.310, the paste without its trailing newline).debug/<sessionId>.txt: per-session debug logs (cc:472977);debug/latestsymlink (local). On Linux 2.1.310 only a session started withclaude --debugwrote them: the log (31 KB after the start, 48.3 KB when the session exited 10 h 52 min later) anddebug/latest, a symlink to it, both kept after the exit. Each line carries a component tag:[STARTUP],[FileIndex],[init],[uds-messaging],[servedCatalog],[keybindings],[MCP],[Bootstrap],[skills],[sessionRegistry],[plugins-scope],[engine],[claudeai-mcp],[plugins],[mcp-registry],[jobStateNameSync],[event-loop-stall],[cc-plugin-telemetry],[auto-mode],[ScheduledTasks]and[Perfetto]. The vendor page listsdebug/among the paths the retention sweep removes (What survives a delete).file-history/<sessionId>/: pre-edit file backups (cc:482923), one<16 hex>@v<n>file per tracked path and version on Windows 2.1.309 (File-history records);session-env/<sessionId>/(cc:480109), an empty directory created at the session's first tool run on Windows 2.1.309 and at its first shell call on Linux and macOS 2.1.310 (mode 0755 on macOS), kept after the exit;shell-snapshots/snapshot-<shell>-<epoch>-<rand>.sh(shell function dumps, cc:480528 region, local name confirms), on Windows a 2.3 KBsnapshot-bash-*of Git Bash (the bash that Git for Windows installs), on Linux 2.1.310 a 7.5 KBsnapshot-bash-<epoch ms>-<6 chars>.sh, written at a session's first Bash call and removed when the console exits, and on macOS 2.1.310snapshot-zsh-<epoch ms>-<6 chars>.sh(3.4 to 3.8 KB, mode 0644), named for the user's login shell, written and removed the same way;todos/,tasks/<sessionId>/,plans/*.md(cc:476220; on Windows 2.1.309plans/<adjective-adjective-noun>.md, a 1.3 KB Markdown plan under a random three-word name, written with theWritetool in plan mode; on Linux 2.1.310 the same form, 1,010 B, written when the plan-mode prompt was a task and not when it was a question, soplans/is not written for every plan-mode session; on macOS 2.1.310 1.4 KB, written for a task by aWritecall after aToolSearch, the directory created at that turn),teams/,jobs/<id>/parent-transcript.jsonl(cc "jobs" ctx),dump-prompts/*.jsonl(full outgoing prompts, cc "dump-prompts" ctx),api-dumps,traces/*.json,usage-data/{facets,session-meta}/*.jsonandreport-*.html(cc:492127),feedback-bundles/*.zip,shares/*.zip,uploads/<sessionId>/,ide/<port>.lock(local13502.lock; Windows/WSL also probes%USERPROFILE%\.claude\ide, cc "ide" ctx; on Windows 2.1.309ide/was created when the VS Code extension activated and the lock removed when VS Code closed, and on Linux 2.1.310 the same,ide/being mode 0700, and on macOS 2.1.310 the same, the lock's keys being under VS Code extension files),backups/(.claude.jsonsnapshots; docs:~/.claude/backups/.claude.json.corrupted.<ts>).backups/.claude.json.backup.<epoch ms>on Windows 2.1.309: a dated full copy of.claude.jsonwritten at every start and exit, the first (84 B,firstStartTimeandfirstStartVersion) by the installer; the housekeeping sweep prunes the oldest, and four to five were kept through the round. Each copy carries the sign-in details of.claude.json(Credentials). On Linux 2.1.310 copies were also written mid-session and at the first launch, the changelog fetch and the sign-in (84, 517, 558 and 2,290 B), and the set was capped at the five newest with no sweep involved: each new copy removed the oldest, as the vendor's Explore the .claude directory says ("keeps the five newest, plus a copy of any version it couldn't parse"). On macOS 2.1.310 the installer wrote two copies, 84 B and then 423 B (addinghasResetAutoModeOptInForDefaultOffer,machineID,migrationVersion,opusProMigrationComplete,seenNotifications,sonnet1m45MigrationCompleteanduserID), the five newest were kept after that, and the copies kept after a/logoutstill held the logged-out account (What survives a delete)..last-cleanup: a 24-byte printable marker, not JSON, rewritten when the housekeeping sweep runs; on Windows 2.1.309 that was at a start, at a turn and on a timer while idle, and the sweep prunedbackups/and the temp root (Scratch directories). On Linux 2.1.310 it was first written 30 minutes into the second session, by the retention sweep, whose timing is under What survives a delete; that sweep also removed the session daemon's emptydaemon/auth/directory. On macOS 2.1.310 it was first written 20 minutes after the second session's first prompt, and that run removed the empty.staging/the first skills sync had left.skills/synced/<org uuid>_<account uuid>/: copies of the claude.ai account's skills, written at the first prompt after sign-in (286 entries, 3.9 MB on Windows 2.1.309:manifest.json,.last-complete-round, a transient.staging/, one directory per skill) and refreshed on a timer, with an emptyskills/synced/.bucket-<org uuid>_<account uuid>beside it. The directory name says which account and organisation the skills came from; collected. On Linux 2.1.310 it was written at the second start (307 entries withplugins/synced/, 7.2 MB; the skills 4.7 MB:manifest.json11.5 KB,.last-complete-round40 B, an empty.staging/, 14 skill directories such asdocx/,pdf/,deep-research/andcomputer-use/), and.last-complete-roundwas rewritten at most starts and on a timer while a session ran (42 minutes after the first sync, then at intervals of hours overnight). On macOS 2.1.310 it was written at the second start (285 entries, 3.9 MB:manifest.json11,500 B,.last-complete-round40 B, 14 skill directories) and refreshed about ten and twenty minutes later while the session ran.state/mcp-discover-verdicts.json: one entry per MCP server the CLI probed (MCP state).~/.config/git/ignore, outside the config home: the user's global git excludes file. Claude Code appends**/.claude/settings.local.jsonto it, creating it when absent: on Windows 2.1.309 it created the file (31 B, the length of that pattern and a newline) in the same second it wrote a project'ssettings.local.json, and on Linux and macOS 2.1.310 the same (31 B, creating~/.config/git/). The collector does not take it, because the file exists on hosts that never ran Claude Code and a catalog line would mark them as Claude Code homes; a responder checks it by hand..claude.jsonsits in$HOME, or inside the config home whenCLAUDE_CONFIG_DIRis set: its path isjoin(process.env.CLAUDE_CONFIG_DIR || homedir(), `.claude${suffix}.json`)(cc:473059mar(), cc:473896configPath; variants.claude${suffix}.json, cc:477075); docs: "holds your sign-in session, MCP server configurations, per-project state such as trust decisions, and the global config keys" (settings page). Itsprojectsobject is keyed by project path (Where the project path is recorded); the keys seen on Windows 2.1.309 are under.claude.json.settings.json,settings.local.jsonand, per project,.claude/settings.jsonand.claude/settings.local.json: settings, including the standing permission grants, which with theprojects.<path>grants in.claude.jsonare the evidence of what the person authorised (issue 93). They are collected with the config home and the project catalog's.claudeentry; their keys are under Permission configuration. The usersettings.jsonon Windows 2.1.309 heldautoUpdatesChannel(written by the installer),theme(first launch),model(the alias picked when/modelwas saved as the default),agentPushNotifEnabled(written when Remote Control started) andcleanupPeriodDays(set by hand). On Linux 2.1.310 it held the same keys butautoUpdatesChannel:themewritten at the first launch (22 B), not by the installer, thenmodel(opus),agentPushNotifEnabledandcleanupPeriodDays(99 B). On macOS 2.1.310:themeat the theme choice (22 B),model(fable) at/model(42 B), rewritten toopusby the VS Code extension's start with no command (Joins and timestamps), andcleanupPeriodDaysset by hand.plugins/:installed_plugins.json,known_marketplaces.json,marketplaces/<name>/git clones,cache/zip cache (cc plugins ctx); local alsoblocklist.json,plugin-directory-cache-v2.json,synced/. On Windows 2.1.309 the first sign-in auto-installed the official marketplace:known_marketplaces.json(283 B,claude-plugins-official{installLocation, lastUpdated, source{source, repo}}withsourcegithub) and the clonemarketplaces/claude-plugins-official/(818 entries, 13.7 MB, with.git);plugin-directory-cache-v2.json(2.6 MB, a download of the plugin directory, refreshed on a timer); andsynced/<org uuid>_<account uuid>/with.marketplaces.json(etag, parserVersion, rows[{id, name, display_name, scope, source{source}, updated_at}],scopedefault,sourceclaudeai: the account's marketplaces) and.last-complete-round, plus a.bucket-file like the skills one. On Linux 2.1.310 the first launch wroteknown_marketplaces.json(274 B) and the clone (819 entries, 16.2 MB), and the second start thesynced/files (.marketplaces.json392 B,.last-complete-round37 B, rewritten at every start) and, during its second turn,plugin-directory-cache-v2.json(2.7 MB); the directory cache andknown_marketplaces.jsonwere refreshed again on timers, once two minutes after a prompt withPlugin autoupdate: checking installed pluginsin the--debuglog. On macOS 2.1.310 the first session start wroteknown_marketplaces.json(275 B) and the official marketplace as a download rather than a clone:marketplaces/claude-plugins-official/with 821 entries and 13.8 MB, no.git, and a 40-byte.gcs-shafile, by its name the hash of the Google Cloud Storage object it came from (.claude-plugin/marketplace.json186.3 KB,plugins/709 entries,external_plugins/102). The second start wrotesynced/(.marketplaces.json391 B,.last-complete-round37 B), andplugin-directory-cache-v2.json(2,718,950 B) came 20 seconds after the session's second prompt, refreshed later on a timer.cache/changelog.md(1.0 MB, written by the installer) andcache/model-catalog/<org uuid>-<12 hex>-cc.json(14 KB,catalog, fetchedAt, staleAt, version, refreshed at starts and at a/modelchange), on Windows 2.1.309. On Linux 2.1.310 the installer fetched nothing: the first launch wrotechangelog.md(withchangelogLastFetchedin.claude.json), and the second start the model catalog (14.4 KB), whosecatalog{config{id, models, provider_alias_targets, settings_vocabulary}, state{id, model, selection_source, thinking, thinking_by_model}, surface}records where the default model came from (Joins and timestamps). On macOS 2.1.310 the first launch wrotechangelog.mdand the first session start after the sign-in the model catalog (14,383 B); a start with no usable sign-in wrotecache/model-catalog/published-floor.json(138 B, mode 0600) andpublished-<32 hex>.json(288,506 B, mode 0600), a catalog not keyed to an account.- Writes with no user action, on Linux 2.1.310 in a
--debugsession left open overnight: the OAuth access token was refreshed (OAuth being the authorisation protocol of the claude.ai sign-in), rewriting.credentials.json(Credentials);.claude.jsonwas rewritten with a backup about five hours after the last prompt; the skills sync ran seven hours after the previous one; and the debug log shows a native-installer update check every 30 minutes (Checking for native installer update to version 2.1.310, thenFound 2.1.310 at ~/.local/bin/claude, skipping install), the skills poll moving to a 30-second idle interval, and an[event-loop-stall]warning (likely sleep/wake). A 24-minute idle with no prompt wrote nothing beyond the start's files. statsig/feature-flag cache,cache/gateway-models.json,cache/model-capabilities.json,telemetry/,telemetry/runs/,chrome/chrome-native-host[.bat],local/(npm-local install),remote/(cloud sessions:/home/claude/.claude/remote/.oauth_token,.api_key,.session_ingress_token, cc.session_ingress_tokenctx).- The session daemon's files and
jobs/: Session daemon files. bridge-spawn/<cse id>/(<cse id>an id startingcse_), on macOS 2.1.310 while a Remote Control server ran: the system prompt it appends to the sessions it spawns and the definition of a remote MCP endpoint whose headers identify the session (Remote Control, Credentials); removed when the server stops, the emptybridge-spawn/staying. Remote Control is the feature that lets claude.ai or the mobile app drive a local Claude Code process through a bridge, the relay through Anthropic's servers that carries the other device's prompts to the local process and its output back.seed-admin/: created empty (mode 0700) byclaude --cloudon macOS 2.1.310 (Cloud sessions).~/Applications/Claude Code URL Handler.app/, outside the config home, on macOS 2.1.310: a 1.1 KB application bundle written at the first/exit, whoseContents/Info.plist(805 B) declaresCFBundleURLTypes[0].CFBundleURLSchemes["claude-cli"],LSBackgroundOnlytrue,CFBundlePackageTypeAPPLandCFBundleVersion1.0besideCFBundleExecutable,CFBundleIdentifier,CFBundleNameandCFBundleURLName, and whoseContents/MacOS/claudeis a symlink to~/.local/bin/claude. It registers Claude Code with Launch Services, the macOS registry of which application opens which URL scheme, as the handler ofclaude-cli:links, and it stays when~/.localis removed, so it marks a host where Claude Code ran. Collected since this round (claude-code|Applications/Claude Code URL Handler.app).
Native installer¶
Native installer binaries: $XDG_DATA_HOME/claude/versions/<ver> (default
~/.local/share/claude/versions) and symlink ~/.local/bin/claude (cc "versions" ctx
o(wje(e),"claude","versions"), o(n,".local","bin"); local confirms three ~245 MB versions).
Windows: winget Anthropic.ClaudeCode (cc:296086), or the native installer
irm https://claude.ai/install.ps1 | iex. On Windows 11 arm64 the native installer of 2.1.309
(2026-10-23) wrote ~\.local\share\claude\versions\2.1.309 (282,697,888 bytes, a win32-arm64 PE
executable, PE being the Windows executable format) and ~\.local\bin\claude.exe, a full second
copy of the same bytes, not a symlink, hard link or junction; ~\.local\state\claude\locks\ and
~\.cache\claude\staging\, both empty; and in the config home the first backups/ copy,
cache/changelog.md, empty downloads/ and sessions/, settings.json and ~\.claude.json. It
added no PATH entry to the user environment (it printed the instruction instead) and wrote nothing
under %LOCALAPPDATA%, %APPDATA%, %TEMP%, Program Files or the registry policy keys. Neither
binary is collected: the versions directory is excluded (Exclusions) and the launcher
copy has no catalog line.
On Ubuntu 26.04.1 arm64 the native installer of 2.1.310 (2026-10-25) runs as
curl -fsSL https://claude.ai/install.sh | bash: the script is #!/bin/bash and uses [[ ]] and
=~, and piped to sh, which is dash on Ubuntu (a smaller POSIX shell), it stops with
sh: 9: Syntax error: "(" unexpected (expecting "then") (exit 2) before writing anything. In
15 seconds it printed Installing Claude Code native build latest...,
Setting up launcher and shell integration..., a note that ~/.local/bin is not in PATH with the
line to append to ~/.bashrc, Version: 2.1.310 and Location: ~/.local/bin/claude, and wrote
~/.local/share/claude/versions/2.1.310 (291,496,120 bytes, an ELF aarch64 executable, ELF being
the Linux executable format, dynamically linked) with ~/.local/bin/claude a symlink to it (47 B),
not the copy of Windows; the empty ~/.local/state/claude/locks and ~/.cache/claude/staging
directories; and in the config home the first backups/ copy, empty downloads/ and sessions/,
and ~/.claude.json (517 B, 12 keys). Unlike the Windows installer it fetched nothing: no
cache/changelog.md, settings.json or changelogLastFetched key until the first launch. It
changed no shell profile; Ubuntu's .profile already adds ~/.local/bin at login when it exists.
On macOS 26.6.2 arm64 the same command (2026-10-26, run over ssh) took 4 minutes 13 seconds, most
of it the download, and printed the Linux output with its setup notes twice, naming ~/.zshrc
where Linux names ~/.bashrc; it created no ~/.zshrc and left ~/.zprofile unchanged. It wrote
~/.local/share/claude/versions/2.1.310 (274,068,224 bytes, a Mach-O 64-bit arm64 executable,
Mach-O being the macOS executable format) with ~/.local/bin/claude a symlink to it (48 B), the
empty locks and staging directories, empty downloads/ and sessions/, ~/.claude.json
(517 B, the twelve Linux keys, installMethod native) and two backups/ copies
(Files that hold evidence); again no changelog.md, settings.json
or changelogLastFetched. The claude process the installer ran left its socket in
/tmp/cc-socks/ (Scratch directories).
~/.local/state/claude/locks/<version>.lock (138 B, mode 0644, JSON acquiredAt, execPath, pid,
version) is a per-version lock held by a running claude process: on Linux 2.1.310 it was written
at each start and removed at each exit, so it exists only while a session runs and names that
process's executable and pid; macOS 2.1.310 did the same. ~/.cache/claude/staging/ stayed
empty whenever listed, on Windows, Linux and macOS alike. Both directories are collected since
this round (claude-code|.local/state/claude
and claude-code|.cache/claude).
Session daemon files¶
Observed on Windows 2.1.309, after a reboot killed an open session and the next start handed its session to the daemon (the records are under Session daemon records):
daemon.lock(431 B JSON),daemon.status.json(121 B) anddaemon.log(0 B until the daemon ended, then 1,370 B of text) in the config home: the supervisor's lock, status and log.daemon/roster.json(3.3 KB with three workers, 88 B with none): the workers and how to reach them, with two auth tokens per worker (Credentials);daemon/control.key(32 B) anddaemon/pipe.key(16 B), raw keys for the daemon's local IPC (inter-process communication);daemon/dispatch/(empty),daemon/pty-pids/<short>.pid(4 B each, one per worker; a PTY is the pseudo-terminal a worker runs in) anddaemon/attach-journal/<uuid>.json(316 B, one per attach of a console to a worker).jobs/pins.json([]),jobs/.draft-<8 hex>(42 B, an unsent prompt draft written at exit), and per workerjobs/<short>/withstate.json(905 B to 1.1 KB),timeline.jsonl(the job's state changes, 445 B to 4.8 KB) and an emptytmp/;<short>is the worker's eight-hex short id.sessions/<pid>.jsonand its.keyfor each worker beside the console's (Remote Control).
The daemon is not a login item: after a reboot no claude.exe ran and nothing restarted it, and its
files stayed as they were left. /exit in a console attached to a worker does not stop the worker,
and at each exit new worker session directories and MCP logs appeared, the daemon starting
replacement workers. On the round the daemon ended (2026-10-23 15:11:45Z) while an ordinary console
session of the same project was running, removing daemon.lock, daemon.status.json, the
pty-pids file and the worker's registry files and leaving roster.json with no workers; what
ended it is not known.
On Linux 2.1.310 no session was handed to the daemon: no claude process remained after any
/exit, a 90-minute session's included, none started at login after the reboot, and
claude --continue resumed the last session in place
(Joins and timestamps).
claude agents started the daemon even with no session to attach to, writing daemon/ (mode
0700) with control.key (32 B of hex, mode 0600), roster.json (89 B, mode 0600, no workers) and
the empty auth/ and dispatch/, and jobs/pins.json ([]); when the console was quit the
supervisor, having no worker, exited and wrote daemon.log (809 B, ten lines of text, mode 0664).
No daemon.lock, daemon.status.json, daemon/pipe.key, daemon/pty-pids/,
daemon/attach-journal/, jobs/<short>/ or jobs/.draft-* was written, and the files stayed as
left through the reboot, except daemon/auth/, which a later sweep removed
(Files that hold evidence).
On macOS 2.1.310 no step started the daemon: one claude process ran per session, none remained
after any exit, and none was running after the reboot.
VS Code extension files¶
The Claude Code extension for VS Code (2.1.309 on Windows, installed from the marketplace on
2026-10-23) lives in ~\.vscode\extensions\anthropic.claude-code-2.1.309-win32-arm64\ (281.6 MB,
58 entries: extension.js, webview\index.js, claude-code-settings.schema.json,
.vsixmanifest, package.json and a resources\ tree holding the CLI binary), which is a copy of
a published package and not collected (the vscode catalog entry takes extensions.json only). It
keeps no VS Code storage of its own: no globalStorage\anthropic.claude-code* directory existed.
Its log is AppData\Roaming\Code\logs\<start>\window1\exthost\Anthropic.claude-code\Claude
VSCode.log (text; exthost is VS Code's extension host process), which the vscode catalog entry
collects under that agent. In the config home it creates ide/ with a <port>.lock while VS Code
runs and removes the lock when VS Code closes; its sessions go to the CLI's projects/ tree
(VS Code extension sessions). The VS Code agent-host and chat files
written at VS Code's own launch are VS Code's (vscode.md).
On Linux the extension (2.1.310 linux-arm64 in VS Code 1.143.1, installed from the marketplace on
2026-10-26) is ~/.vscode/extensions/anthropic.claude-code-2.1.310-linux-arm64/ (297 MB:
resources/native-binary/claude, 291,496,120 B, the size of the installed CLI binary,
extension.js 3.7 MB, webview/index.js 6.0 MB, claude-code-settings.schema.json,
.vsixmanifest, package.json, README.md and images), again with no
globalStorage/anthropic.claude-code* directory. Its log is
~/.config/Code/logs/<start>/window1/exthost/Anthropic.claude-code/Claude VSCode.log (53 KB, 579
lines). Its start wrote one connector MCP log per server and a skills sync, its session went to the
CLI's projects/ tree, and ide/ was empty again after VS Code closed. VS Code 1.143 itself wrote
~/.copilot/logs/ at its first start, before the extension was installed
(Per-user storage in copilot-cli.md).
On macOS code --install-extension anthropic.claude-code (2026-10-26) installed 2.1.310 as
~/.vscode/extensions/anthropic.claude-code-2.1.310-darwin-arm64/ (58 entries, 273.3 MB, the CLI
binary of the same build inside) and rewrote extensions.json (751 B). The extension's session
ran that bundled binary, resources/native-binary/claude, as a child of VS Code's extension host,
with the usual registry file (entrypoint claude-vscode), .key, version lock and socket. While
it ran, ide/<port>.lock (181 B) held authToken (a uuid), ideName, runningInWindows,
transport (ws, a WebSocket) and workspaceFolders (one path): the extension's listener for the
CLI and the token that authenticates to it (Credentials). Quitting VS Code removed
the lock, the empty ide/ staying. There was again no globalStorage/anthropic.claude-code*; the
agent-host-config.json, agent-host-storage.json, chatSessions/ and chatEditingSessions/
files VS Code wrote mention no Claude and are VS Code's own (vscode.md).
CLI cache directory¶
The CLI cache directory (issue 81) comes
from the env-paths code bundled in the CLI with app name claude-cli and suffix nodejs:
~/Library/Caches/claude-cli-nodejs/ on macOS (observed with CLI 2.1.295 launched by the Claude
Desktop Code tab, 2026-10-11), $XDG_CACHE_HOME/claude-cli-nodejs/ else
~/.cache/claude-cli-nodejs/ on Linux (observed on Linux 2.1.310), and
%LOCALAPPDATA%\claude-cli-nodejs\Cache\ on Windows (observed on Windows 2.1.309). Under it,
<project slug>/mcp-logs-<server>/<timestamp>.jsonl: the slug is the cwd with every
non-alphanumeric character replaced by - (the projects/ key scheme), the server is the MCP
server name slugged the same way, and the file name is an ISO timestamp with : and . replaced by
- (2026-10-12T02-23-51-709Z.jsonl).
Each line is {cwd, debug, sessionId, timestamp}, or {cwd, error, sessionId, timestamp} for an
error (seen on Windows 2.1.309). The bundle also names <project slug>/errors/ (not observed).
CLAUDE_CONFIG_DIR does not move it. Observed on Windows 2.1.309: every process start (each
console, worker, resume and VS Code extension session) wrote one new file for each of the account's
two claude.ai connectors, mcp-logs-claude-ai-Claude-Docs\ and mcp-logs-claude-artifacts\ under
C--Users-sean-lab-proj\ (9 to 12 lines each, two of them error lines in the second), from the
first sign-in on, so the connector logs are account state on every surface; a local-scope stdio
server added with claude mcp add logged to mcp-logs-fs\, one file of 0.9 to 2.0 KB per server
start. Observed on macOS: the Code tab's CLI wrote
mcp-logs-ccd-host/ and mcp-logs-terminal/ (the desktop's own two MCP servers) under the
project's slug at session start, one line of about 200 bytes each, and ordinary CLI sessions on
another project wrote mcp-logs-<connector name>/ files of 7-9 lines (1.5-2.2 KB); the whole
directory was 16 KB. Observed on Linux 2.1.310: from the first sign-in on, every start (each
console, resume, --continue, the VS Code extension) wrote one file per claude.ai connector of the
account under ~/.cache/claude-cli-nodejs/-home-sean-lab-proj/ (mcp-logs-claude-ai-Claude-Docs/
9 lines, mcp-logs-claude-ai-Monarch/ 8, mcp-logs-claude-artifacts/ 12 with two error lines),
and the local-scope stdio server fs logged to mcp-logs-fs/ (2,302 B, 10 lines, one of them an
error); no errors/ directory appeared. Observed on macOS 2.1.310 on a clean install:
~/Library/Caches/claude-cli-nodejs/-Users-sean-lab-proj/ got, from the first session start on
and before any prompt, one file per claude.ai connector of the account per start (9, 8 and 12
lines, two of them error lines); claude mcp list, a health check of every server, wrote one for
every connector of the account, three of which no session had logged, and one under mcp-logs-fs/
(175 B) for the local server, whose session start then logged 2.0 KB (six debug lines and one
error); a Remote Control server added mcp-logs-claude-code-remote/ (3.0 KB, 11 debug lines)
for its remote endpoint (Remote Control); and a start with no usable sign-in
logged the local server only. Evidence, claude 2.1.295 linux-arm64 (the binary
recorded in
Source and evidence):
@206335642, env-paths with the three platform branches:F=(t)=>{let e=n.join(d,"Library");return{data:n.join(e,"Application Support",t),config:n.join(e,"Preferences",t),cache:n.join(e,"Caches",t),log:n.join(e,"Logs",t),temp:n.join(_,t)}},P=(t)=>{let e=g.APPDATA||n.join(d,"AppData","Roaming"),r=g.LOCALAPPDATA||n.join(d,"AppData","Local");return{data:n.join(r,t,"Data"),config:n.join(e,t,"Config"),cache:n.join(r,t,"Cache"),log:n.join(r,t,"Log"),temp:n.join(_,t)}}and, at@206336051,G=(t)=>{...cache:n.join(g.XDG_CACHE_HOME||n.join(d,".cache"),t),log:n.join(g.XDG_STATE_HOME||n.join(d,".local","state"),t)...}.@206336334:function f(t,{suffix:e="nodejs"}={}){...if(e)t+=`-${e}`;if(E.platform==="darwin")return F(t);if(E.platform==="win32")return P(t);return G(t)}import{join as h}from"path";var k=f("claude-cli"),O=200;function N(t){let e=t.replace(/[^a-zA-Z0-9]/g,"-");if(e.length<=O)return e;return`${e.slice(0,O)}-${Math.abs(yse(t)).toString(36)}`}.@206336793:function y(){return h(k.cache,N(n2n()))}var yut={baseLogs:()=>y(),errors:()=>h(y(),"errors"),mcpLogs:(t)=>h(y(),`mcp-logs-${N(t)}`)}.
Managed policy¶
System-wide managed policy (outside the home): /Library/Application Support/ClaudeCode/,
/etc/claude-code/, C:\Program Files\ClaudeCode\ holding managed-settings.json,
managed-settings.d/, managed-mcp.json (cc:412717-9; docs /managed-settings). MDM plist domain
com.anthropic.claudecode; registry HKLM|HKCU\SOFTWARE\Policies\ClaudeCode\Settings (docs).
Relocated config homes (CLAUDE_CONFIG_DIR)¶
With CLAUDE_CONFIG_DIR set, every file above moves into that directory, .claude.json included
(cc:473059, cc:473896); the one exception is .device-keys.json, which stays at
~/.claude/.device-keys.json (cc:477075 E9n() joins homedir(), ".claude"). Third-party tools
that read Claude Code state resolve .claude.json the same way
(claude-mem observed-billing.ts).
The catalog covers a relocated home only when it is a .claude-<name> directory in the home, such
as ~/.claude-work, and only through the Claude-specific names inside it: .claude-*/projects,
.claude-*/file-history, .claude-*/history.jsonl, .claude-*/.claude.json*,
.claude-*/.credentials.json, .claude-*/hfi-auth.json, .claude-*/.session_ingress_token,
.claude-*/remote/.oauth_token, .claude-*/remote/.api_key, .claude-*/sessions/*.*.key (the
<pid>.<sha256>.key shape), .claude-*/daemon/control.key, .claude-*/daemon/pipe.key,
.claude-*/daemon/roster.json and .claude-*/backups/.claude.json.*. A bare .claude-* entry
would claim other tools' directories that share the prefix:
- claude-code-router:
~/.claude-code-routeron macOS and Linux,%APPDATA%\claude-code-routeron Windows (app-paths.ts;packages/cli/README.md). Top level:config.sqlite,app-data/,service.json,gateway.config.json,profiles/,bin/, plusconfig.json,app-cache,bot-gateway,grok-mediaand a few state JSON files; noprojects/,history.jsonl,file-history/,.claude.jsonor.credentials.jsonat its root. It does run Claude Code withCLAUDE_CONFIG_DIRset to~/.claude-code-router/profiles/<slug>[/custom]/claude(launch-core.ts, env); those homes are three levels down and are not matched by the.claude-*/lines. - claude-mem:
~/.claude-mem(paths.ts) holdssettings.json(paths.ts), which can carry provider API keys (OpenAICompatProvider.ts), plusclaude-mem.db,logs/,chroma/and.env. Itssettings.jsonis why.claude-*/settings.jsonis not a catalog line; it has noprojects/orhistory.jsonl. - claude-squad:
~/.claude-squad(config.go) holdsconfig.json,state.jsonandinstances.json(config.go,state.go).
Not collected from a relocated home: settings.json, settings.local.json, the <pid>.json
registry files of sessions/ (its *.key files are), todos/, plans/, tasks/,
session-env/, shell-snapshots/, debug/ and the other subdirectories, and any config home
outside the home directory or not named .claude-<name>.
Used by other agents¶
Other agents read this agent's config home or drive the CLI (from source; none seen on a real install):
- Crush reads
~/.claude/skillsas one of its global skills directories, beside~/.agents/skills, and a project's.claude/skillsas a project one (internal/config/load.go:1312-1322,1345-1350), so a skill in the config home or the project may have been loaded by Crush as well as by Claude Code (crush.md). - Agent Zero's Orchestrator plugin, which tells that agent how to drive other coding CLIs on the
host or in its container, defaults Claude Code to
permission_mode: bypassPermissionsand, when not running as root, runsclaude -p "$TASK" --output-format json --permission-mode bypassPermissions --allowedTools Bash,Read,Edit(plugins/_orchestrator/default_config.yaml:7-37,plugins/_orchestrator/README.md:96). Such a session is an ordinary transcript underprojects/, started by another agent with approvals bypassed; the plugin's permission settings are in Parser plan inagent-zero.md. - Hermes Agent, Cline, Pydantic AI's CLAI (its command-line chat client) and Zed copy or index the transcripts (What survives a delete).
Credentials¶
- macOS: Keychain generic password, account = OS user, service =
"Claude Code"+"-credentials"(keyed to the config dir when non-default), read withsecurity find-generic-password -a <user> -w -s <service>(cc:473906, cc:494949,HAn="-credentials"chunk near cc:473906). Observed on macOS 2.1.310 (2026-10-26): a session started from Terminal.app in the desktop session stored the token in the login keychain as a generic password with serviceClaude Code-credentialsand account the OS user name, wrote no.credentials.json, and left the item in place after it exited. A session started over ssh, which cannot use the login keychain, wrote.credentials.jsoninstead (559 B, mode 0600, the Windows and Linux key set) and created no keychain item, and an ssh-launched start after the desktop sign-in found neither store and came upNot logged in · Run /login: the two launch paths do not share a store, and a macOS home whose user signs in from the desktop holds no Claude Code token file at all. The file's modification time stayed at the sign-in for two hours, and/logoutremoved it (What survives a delete). Fallback and Linux/Windows:<config>/.credentials.jsonmode 0600 (docs /iam). Local file present. On Windows 2.1.309 it was written at the browser sign-in (559 B) and rewritten at each token refresh; its fields are in Secrets. On Linux 2.1.310 it was written at the sign-in (559 B, mode 0600, the Windows key set) and rewritten overnight in an idle session when the access token was refreshed, at the same size: the file's modification time is not the sign-in time, and only its hash shows that it changed. ~/.claude/.device-keys.json(device key store, file fallback to keychain, cc:477075, cc:337890-900). Not moved byCLAUDE_CONFIG_DIR. Not written by the sign-in or at any later step on Windows 2.1.309..claude.jsonholds the sign-in session and MCP server definitions (docs; cc:406053 "refusing to write to avoid wiping ~/.claude.json" auth) and is flaggedsecret: true; its credential fields are in Secrets.hfi-auth.json,.session_ingress_token,remote/.oauth_token,remote/.api_key(cloud/host handoff),CLAUDE_CODE_HOST_CREDS_FILEpath (cc:272494, 276584 region), Claude Desktophost-creds-*.json(see claude-desktop).<config>/sessions/<pid>.<sha256>.key(139 bytes on Linux, 108 bytes on macOS) beside the per-processsessions/<pid>.json, observed on real installs with CLI 2.1.295 launched from the Claude Desktop Code tab on Linux and on macOS, 2026-10-11 (see issue 76 andclaude-desktop.md); flaggedsecret: true(.claude/sessions/*.key,.claude-*/sessions/*.key). On Windows 2.1.309 it is 118 B, a JSON object whose fields are in Secrets, and is removed with its<pid>.jsonwhen the process exits. On Linux 2.1.310 it is 138 or 139 B, mode 0600, in asessions/of mode 0700, a JSON object with a different third key (Secrets), removed at the exit in the same way; on macOS 2.1.310 it is 108 B, mode 0600, the Linux key set (Secrets). The<pid>.jsonitself is not flagged (Remote Control).- The session daemon's
daemon/control.key,daemon/pipe.keyanddaemon/roster.json(Session daemon files): the first two are raw keys, the roster holds two auth tokens per worker. Flaggedsecret: truesince this round (.claude/daemon/control.key,.claude/daemon/pipe.key,.claude/daemon/roster.jsonand their.claude-*/forms). backups/.claude.json.backup.<epoch ms>(Files that hold evidence): each a full copy of.claude.jsonwith its sign-in details, flaggedsecret: truesince this round (.claude/backups/.claude.json.*and the.claude-*/form, which also takes the.claude.json.corrupted.<ts>copy of Per-user storage). The home-level.claude.json.backup*files of the older layout, beside~/.claude.json, are flagged with them. On macOS 2.1.310 the copies kept the account block after a/logout.ide/<port>.lock(itsauthToken) andbridge-spawn/<cse id>/mcp.json(the headers that identify a session to the remote MCP endpoint), each on disk only while the VS Code extension or a Remote Control server runs (VS Code extension files, Remote Control): flaggedsecret: truesince this round (.claude/ide/*.lock,.claude/bridge-spawn/*/mcp.jsonand their.claude-*/forms).- The GitHub account Claude Code acts through, and its token, are in the
ghtool's store, not in Claude Code's files; on macOS that store is a plaintexthosts.ymlor a keychain item depending on howghwas launched (Source-host integration). - Config files that can embed keys:
settings.json/settings.local.json,.mcp.json,managed-mcp.json(the fields are in Secrets)..claude/settings.jsonand.claude/settings.local.json, in the home and in a project, and a project's.mcp.jsonare flagged. A relocated~/.claude-<name>home'ssettings.jsonis not collected, so it needs no glob;managed-mcp.jsonlives in the system directory outside every home (Managed policy). Env:ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN,CLAUDE_CODE_OAUTH_TOKEN(docs /iam precedence list). Anthropic profiles live outside:~/.config/anthropic/%APPDATA%\Anthropic(docs /iam).
Exclusions¶
Patterns are relative to the home: .claude/plugins/marketplaces (git clones, 13.7 MB for the
official marketplace on Windows 2.1.309; on macOS 2.1.310 a 13.8 MB download with no .git),
.claude/plugins/cache,
.claude/plugins/plugin-directory-cache* (the 2.6 MB download of the plugin directory, excluded
since this round), .claude/plugins/*/node_modules, .claude/local/node_modules,
.claude/image-cache, .claude/chrome, .claude/cache (the changelog and the model catalog),
.claude/statsig (flag cache, optional), .local/share/claude/versions (~245 MB per version, 282.7
MB for 2.1.309 win32-arm64, 274 MB for 2.1.310 darwin-arm64). .claude/paste-cache is collected
since this round: history.jsonl
names a long paste by its hash alone, so once the transcript is gone the paste cache holds the only
copy of the text (history.jsonl). skills/synced/ is a copy of account state
and is collected, as the list of skills the agent could use on the host. Project-side:
*/.claude/worktrees, */.claude/checkpoints (gitignore template cc "claude-code-runtime").
downloads/, uploads/, file-transfers/ may be large but are user content; cap by size rather
than exclude. The CLI cache directory is small and nothing in it, its
errors/ subdirectory included, is excluded. On Windows the catalog names the cache directory's
children (AppData/Local/claude-cli-nodejs/Cache/*) rather than the directory, so that the Claude
Desktop exclusion */Claude*/Cache does not prune it
(collectors/docs/coverage.md says why). The VS Code
extension's copy of the CLI binary
(.vscode/extensions/anthropic.claude-code-*/resources/native-binary/claude, 291 MB on Linux
2.1.310, most of the 273.3 MB extension directory on macOS 2.1.310,
VS Code extension files) is a copy of a published binary; no
catalog entry reaches the extensions directory (the vscode entry takes only extensions.json
from it), so the copy is not collected and needs no exclusion.
Project-local files¶
.claude/settings.json, .claude/settings.local.json,
.claude/{skills,commands,agents,hooks,rules}/ (cc "agents"/"settings.json" ctx), .mcp.json
(cc:273219), CLAUDE.md, CLAUDE.local.md (cc:19360, 299964), .claude/scheduled_tasks.json,
.claude/scheduled_tasks.lock, .claude/routines/.state/, .claude/worktrees/,
.claude/checkpoints/, .claude/mailbox/, .claude/agent-registry.json,
.claude/agent-memory-local, .claude/first-run, .claude/assistant-daemon-state.json (gitignore
template, cc "claude-code-runtime" ctx). The built-in agents-md mod also reads AGENTS.md
and .claude/AGENTS.md as project instructions, by default where a project has no CLAUDE.md
(mods/agents-md/hooks/names/agents-names.ts:5);
the collector gathers them through the shared project|AGENTS.md entry and claude-code|.claude.
No per-project database.
Observed on Windows 2.1.309:
.claude/settings.local.jsonwas created by the first allow-always answer (87 B, onepermissions.allowrule, Permission configuration), and in the same second Claude Code created the user's~/.config/git/ignoreto keep that file out of commits (Files that hold evidence)..claude/scheduled_tasks.lock(123 B JSON,acquiredAt, pid, procStart, sessionId) was written by/loop; noscheduled_tasks.jsonwas left beside it after the task was deleted (Scheduled tasks)..git/info/excludewas rewritten in the same second as the lock: git's six-line default template plus eleven exclude patterns that nameclaude(541 B, 17 lines). It is git metadata and is collected with the project's other.git/files since this round (project|.git/info/exclude).- Removing the project directory removes these files and nothing in the home (What survives a delete).
Observed on Linux 2.1.310: the allow-always answer created .claude/ and
.claude/settings.local.json (72 B, mode 0664, one rule,
Permission configuration), with ~/.config/git/ignore in the same
second; /loop wrote .claude/scheduled_tasks.lock (112 B, the same four keys), which was left
behind after the loop was stopped, with no scheduled_tasks.json beside it
(Scheduled tasks). Removing the project (52 entries) removed these files and
nothing in the home.
Observed on macOS 2.1.310: the allow-always answer created .claude/settings.local.json (72 B, mode
0644, one 15-character rule) with ~/.config/git/ignore in the same second; /loop wrote
.claude/scheduled_tasks.lock (130 B, mode 0644, the same four keys), removed at /exit, and
appended ten patterns to the repository's own .git/info/exclude (541 B), which name the
project-local files the build can write: **/.claude/scheduled_tasks.lock,
**/.claude/scheduled_tasks.json, **/.claude/routines/.state/, **/.claude/worktrees/,
**/.claude/checkpoints/, **/.claude/mailbox/, **/.claude/agent-registry.json,
**/.claude/agent-memory-local, **/.claude/first-run and
**/.claude/assistant-daemon-state.json. claude --cloud rewrote one tree object under
.git/objects/ (the same size, a new modification time, no new commit), and claude --teleport
stashed the uncommitted change with git stash, leaving .git/refs/stash, .git/logs/refs/stash
and .git/ORIG_HEAD and the stash message Teleport auto-stash
(Cloud sessions).
Removing the project (62 entries) removed these files and nothing in the home.
Where the project path is recorded¶
~/.claude.json→projectsobject, keys are absolute cwd paths (cc:474276e.projects; docs "per-project state such as trust decisions"). In a relocated home it is<config home>/.claude.json;discover_projectsandFind-Projectsread~/.claude-*/.claude.jsonand~/.claude-*/history.jsonlas well as the default paths. On Windows 2.1.309 the key is the native path with backslashes and the drive letter (C:\Users\sean\lab\proj), written at the trust dialog of the first launch, and the transcript directory for it isprojects/C--Users-sean-lab-proj/(issue 112); a start in the home that stopped at the trust prompt added no key. On Linux 2.1.310 the key is the plain absolute path (/home/sean/lab/proj), written at the trust dialog, with the transcript directoryprojects/-home-sean-lab-proj/; a start in the home that stopped at the trust prompt added no key, noprojects/-home-sean/directory and no history line. On macOS 2.1.310 the key is/Users/sean/lab/proj, written at the trust dialog with eleven keys, with the transcript directoryprojects/-Users-sean-lab-proj/, and a start in the home that stopped at the trust prompt again wrote nothing.projects.<path>.lastSessionIdnames the last session run there (.claude.json). Theenvironmentattachment'ssnapshot.scratchpadDirectoryspelled the same project's slug two ways across sessions on Windows 2.1.309,C--Users-sean-lab-projandc--Users-sean-lab-proj(the drive letter's case differs), so a project's scratch directories can differ only by case (issue 112).~/.claude.json→githubRepoPaths,{"<owner>/<repo>": ["<local path>", ...]}: every GitHub repository the CLI has seen as a checkout'soriginremote, with the local checkouts of it. On Windows 2.1.309 it was filled at the first launch in the lab checkout, with no GitHub sign-in, and still listed the path after the checkout was removed; Linux and macOS 2.1.310 did the same.history.jsonl→projecton every line (history.jsonl).- Transcript records: every
projects/<key>/<id>.jsonlrecord carries"cwd"(cc:482870 parser; the envelope is in Record schema). The directory key is lossy (dashes), so readcwdrather than decode the key. - CLI cache directory: every
mcp-logs-*/<timestamp>.jsonlline carriescwd, a project path, and the slug directory above it is the lossy key of the same path.discover_projectsandFind-Projectsdo not read it. - Claude Desktop
claude-code-sessions/records each Code session's working folder (docs data-storage) and points back to~/.claude/projects/.
Parsing¶
Transcript stores¶
The config home is $CLAUDE_CONFIG_DIR, else ~/.claude, on every OS
(Per-user storage). Relative to the home:
| Path | Content | Parse |
|---|---|---|
.claude/projects/<key>/<session id>.jsonl |
the session transcript; <key> is the cwd with every [^a-zA-Z0-9] replaced by -, cut at 200 characters plus - and a base-36 hash when longer (cc:473717, cc:474429); the session id is a UUID (cc:477784 ${q()}.jsonl) |
yes |
.claude/projects/<key>/<session id>/subagents/agent-<agent id>.jsonl |
one transcript per subagent, same record format; an optional extra directory level sits between subagents/ and the file when the agent has a transcript subdirectory (cc:477784 agentTranscriptSubdirs, cc:484031 parses subagents/<dirs...>/agent-(.+).jsonl) |
yes |
.claude/projects/<key>/<session id>/subagents/agent-<agent id>.meta.json |
one JSON object per subagent: agentType, description, toolUseId, model, spawnDepth, requestShape, requestNonInteractive, and for some isFork, parentAgentId, spawnedWithWorktree, worktreePath, worktreeBranch (observed, 80 of 80; cc:484010 maps .meta.json to .jsonl); on Windows 2.1.309 agentType, description, finished, launchedWithDefaults, requestNonInteractive, requestShape, spawnDepth, toolUseId, with no model (226 B); on Linux 2.1.310 the same plus isBuiltIn (243 B, requestShape background), and on macOS 2.1.310 the Linux set (243 B) |
no, Joins and timestamps |
.claude/projects/<key>/<session id>/tool-results/<name>.txt |
full output of a tool result too large to keep inline (tool-results constant, cc:474483); the inline result holds a <persisted-output> stub (cc:382991); on Windows 2.1.309 <name> is nine base-36 characters (1.2 MB for a 20,000-line listing; on Linux 2.1.310 191,741 B for 20,000 lines of a word list) |
yes, Persisted output files |
.claude/projects/<key>/<session id>/custom-title.json |
{customTitle}, the name given with /rename (27 B on Windows 2.1.309 and macOS 2.1.310), beside the transcript's custom-title line |
no; the line is read |
.claude/projects/<key>/bridge-pointer.json |
{environmentId, pid, procStart, sessionId, source}, environmentId 28 characters starting env_, written when a Remote Control server starts in the project and left after it stops (macOS 2.1.310, Remote Control) |
yes, Sync, relay and origin rows |
.claude/projects/<key>/<session id>/ccr-tip.json |
{eventId, updatedAt}, the last claude.ai event the local side of a Remote Control session processed (macOS 2.1.310, Remote Control) |
yes, Sync, relay and origin rows |
.claude/jobs/<short>/state.json, .claude/jobs/<short>/timeline.jsonl |
a daemon worker's job state and its state changes (Session daemon records) | no |
.claude/projects/<key>/<session id>/remote-agents/remote-agent-<id>.meta.json, .../mcp-tasks/mcp-task-<id>.meta.json |
sidecars for remote agents and MCP tasks (cc:484010, cc:488428) | no; not observed |
.claude/projects/<key>/memory/*.md |
per-project memory files (observed) | no |
.claude/history.jsonl |
prompt history across sessions, Other stores | yes, as history |
.claude/sessions/<pid>.json |
live-process registry, one file per running CLI process; its keys are under Remote Control | yes, one system row per file, Parser plan |
.claude/jobs/<id>/parent-transcript.jsonl |
a copy of the parent transcript handed to a background job (cc:474968) | no; not observed |
.claude/dump-prompts/<session id>.jsonl |
full outgoing prompts, when that debug option is on (cc:487015) | no; not observed |
<CLI cache directory>/<key>/mcp-logs-<server>/<timestamp>.jsonl |
MCP server log of the CLI, outside the config home; paths per platform and line shape under CLI cache directory (issue 81) | no; a possible source of system rows (Parser plan) |
The parser selects projects/[^/]+/.*\.jsonl$ and history.jsonl under .claude or a
.claude-<name> home, so it reads main and subagent transcripts at any depth, ignores the
.meta.json sidecars, and reads a .txt sidecar only when a stub names it
(Parser plan gives every glob). A config home moved with CLAUDE_CONFIG_DIR to a
.claude-<name> directory in the home (for example ~/.claude-work) is matched by the parser as
far as it is collected (Relocated config homes); a
config home elsewhere or under another name is neither collected nor parsed.
One file is one session id. Observed on Windows 2.1.309: a start and /exit with no prompt still
writes a six-line transcript (mode, permission-mode, three user lines of the /exit command,
a file-history-snapshot) and a history.jsonl line; /clear starts a new file in the same
process; claude --resume appends to the chosen session's file under the same sessionId; a
session handed to a daemon worker continues in the worker's file, and the console's file ends with
a continued-in line (Session daemon records). After a reboot killed an
open session its file stayed as it was and the next start opened a new session, with nothing on
screen about the old one. Observed on Linux 2.1.310 the same, with no daemon: the empty session
writes the same six lines (2,060 B) and history line; /clear starts a new file in the same
process; a resumed or continued session stays in its own file
(Joins and timestamps). The transcript and subagent files
are mode 0600, the .meta.json and tool-results/ files 0664. /rename wrote custom-title.json
(27 B) and a custom-title and an agent-name line.
Observed on macOS 2.1.310 the same: the empty session's six lines (2,063 B) and history line, a new
file at /clear, and --resume and --continue appending to the session's own file. A session a
Remote Control server spawned and a cloud session brought in with --teleport each have a file of
their own, the first created empty when the server connects, the second only at the first local
turn (Remote Control, Cloud sessions). The entrypoint on a
session's lines says which surface wrote it: cli for the console, sdk-cli for a session a
Remote Control server spawned and claude-vscode for the VS Code extension; a teleported
session's lines say cli, and its replayed cloud lines carry remoteSourced
(Transcript envelope).
VS Code extension sessions¶
A session started from the VS Code extension's panel (2.1.309 on Windows, 2026-10-23) is an
ordinary transcript in the same projects/<key>/ directory as the CLI's, so one catalog agent
covers both. Every line carries entrypoint claude-vscode where the CLI's carry cli, and its
user prompt lines promptSource sdk with origin.kind human and turnOrigin human. It has
no system, mode, permission-mode or continued-in line (not even turn_duration); its
environment attachment has changes beside snapshot, its deferred_tools_delta adds
needsAuthMcpServers, and its shell tool call was Bash where the CLI session on the same host
used PowerShell. It took the permission mode and the default model the CLI had left in
settings.json (auto, so no approval was asked). The extension also started a second session id
in the same second, with a temp directory and no transcript
(Scratch directories), and the session's connector MCP logs are the same as
a CLI session's (CLI cache directory). .claude.json
clientDataCacheSlots gained an entry whose entrypoint is claude-vscode
(.claude.json). On Linux 2.1.310 (2026-10-26) the extension's session file
(335,769 B, 43 lines) again carried entrypoint claude-vscode on every envelope line,
promptSource sdk on the prompt and no mode or permission-mode line; it held a cost-state
line, queue-operation lines without content
(queue-operation records), and its Bash call was allowed by the
classifier in auto mode. Its prompt is not a history.jsonl line
(history.jsonl). On macOS 2.1.310 (2026-10-26) the session file (335,118 B, 42
lines) again carried entrypoint claude-vscode on every line, promptSource sdk with
origin.kind human and permissionMode auto on the prompt, a queue-operation pair, and a
Bash call allowed by the classifier; its model was claude-opus-5-5, because the extension's
start had rewritten the user settings.json model from the CLI's saved fable to opus
(Joins and timestamps), and it made two temp-root directories
(Scratch directories).
What survives a delete¶
The retention setting cleanupPeriodDays (default 30, minimum 1; cc:474049 setting description,
cc:490770 cleanupPeriodDays??) drives a sweep that deletes old session data. The vendor's
Explore the .claude directory
lists what it deletes once older than the period: projects/<project>/<session>.jsonl with its
.orphaned-* and .superseded-* copies, the session's subagents/ (with the parent),
tool-results/ and mcp-tasks/, file-history/<session>/ (keeping the 100 most recent
checkpoints and each file's first snapshot), plans/, debug/, paste-cache/,
image-cache/<session>/ (2.1.274 and earlier; later builds keep images under the temp directory's
per-session images/), uploads/<session>/ (Remote Control attachments), dev-mods/<session>/,
session-env/, tasks/, shell-snapshots/ (removed at a clean exit; the sweep clears what a
crash left), backups/ (the five newest kept), feedback-bundles/, feedback/drafts/,
usage-data/, skills/.trash/, plugins/.trash/, plugins/installed_plugins.set-aside.* and
.unreadable.*, and the legacy todos/, statsig/ and logs/. Its own rules: sessions/ loses
each file when its session exits and a crash's leftovers at the next launch; the auto memory
projects/<project>/memory/ is kept, the directory removed only when it stayed empty for the whole
period (before 2.1.228 the sweep could delete files beneath it); Claude Desktop and Cowork
transcripts are kept at any age unless desktopSessionCleanupPeriodDays (2.1.248 and later),
managed settings or the HIPAA configuration (the setup for health data under the United States
HIPAA rules) say otherwise; and a session's scratchpad goes with its transcript
(Scratch directories). The page keeps until the user deletes them
history.jsonl (older entries are removed only under the HIPAA configuration),
stats-cache.json, remote-settings.json (deleted at logout), cache/changelog.md and
policy-limits.json, and names as state files to keep .credentials.json, agent-memory/,
jobs/ and daemon/. The sweep is skipped in --bare mode and paused when the period cannot be
determined (/status warns).
Settings reference, cleanupPeriodDays
says Claude Code "runs the deletion as a background sweep after a session starts" and "deletes
transcripts without showing a message";
Monitoring usage, Retention sweep event
says it runs at most once per session, that a sweep not finished when the session exits emits
nothing, and that from 2.1.227 it emits the OpenTelemetry event claude_code.retention_sweep
(OpenTelemetry being the telemetry export standard of Claude Code's monitoring) with result
(complete or skipped), skip_reason (user_source_disabled, settings_unknowable,
settings_invalid_key_set), period_days, used_default, transcripts_deleted,
transcripts_exempted_desktop, session_files_deleted, artifacts_deleted,
files_retained_fresh, files_past_cutoff and error_count.
The user's own delete is claude purge <project> (claude project purge before 2.1.288, on the
same page): it deletes the project's projects/ tree and auto memory, the per-session tasks/,
debug/ and file-history/, the matching history.jsonl lines and the project's ~/.claude.json
entry, and leaves shell-snapshots/, backups/, the temp directory and .heapsnapshot files.
Neither round ran it.
Observed on Linux 2.1.310 (2026-10-25 and 2026-10-26), with cleanupPeriodDays set to 1 and the
empty transcript of the first session backdated three days: the sweep ran at 03:34:43Z, in a
claude --debug session started at 02:37:33Z whose one prompt was at 03:14:31Z. It deleted that
transcript and its session's scratchpad directory under the temp root, rewrote .last-cleanup and
kept the session's history.jsonl line; nothing else was old enough to go. The debug log's only
trace of it is one line at 03:34:43.431Z,
settings.read: the engine's merge is not raised again (a retention sweep starts): neither the loaded plugins nor the settings changed.
Over the round the sweep ran in three sessions, each 10 to 20 minutes after that session's last
prompt (23:15:06Z to 23:25:46Z, 01:21:28Z to 01:32:08Z, 03:14:31Z to 03:34:43Z), and in none of the
four sessions that had no prompt: two that exited within a minute, the second after .last-cleanup
had been rewritten by hand to a week earlier, one left idle for 24 minutes, and the first 37
minutes of the --debug session, whose log has no retention, cleanup or housekeeping line in that
time. The vendor's "after a session starts" is the claim; the condition observed on 2.1.310 Linux
is a session with at least one prompt.
On macOS 2.1.310 (2026-10-26) .last-cleanup was written twice, each time about 20 minutes after a
session's first prompt: at 16:39:36Z in a session whose first prompt was at 16:19:31Z, removing
nothing visible but the empty .staging/ of the skills sync, since every file was younger than the
30-day default; and at 18:03:08Z, 20 minutes 7 seconds after the 17:43:01Z prompt of a session
started with cleanupPeriodDays 0, removing nothing. 2.1.310 rejects 0 with the message quoted
below, and on macOS the start then ran a settings fix of its own: the session began with the prompt
Help me fix these Claude Code settings issues., which the person had not typed, quoting the
settings error with a guard that the quoted text is configuration data and not instructions; the
model read settings.json through the shell and asked, offering Set it to 3650 (Recommended),
Remove the setting, Don't change anything, Type something and Chat about this, and the
question was declined. The vendor's page says the sweep is paused while cleanupPeriodDays is set
with a settings error, so the marker alone does not say whether that run paused or found nothing
older than 30 days. The run with a backdated session was not repeated on macOS, and the vendor's
list above is the account of what the sweep removes.
Since 2.1.309 the setting cannot be 0:
on Windows (2026-10-23) a settings.json with "cleanupPeriodDays": 0 stopped the start with a
Settings Error screen reading cleanupPeriodDays: Number must be greater than or equal to 0, then
cleanupPeriodDays must be at least 1. To keep transcripts for a long time, set a large number (e.g.
3650 for ~10 years). To disable transcript writes entirely, remove this setting and use the
--no-session-persistence CLI flag or the SDK persistSession:false option instead. (0 is rejected
because it previously silently disabled all transcript writes, which users setting it to mean
"never clean up" did not expect.) and Files with errors are skipped entirely, not just the invalid
settings., with the choices Fix with Claude, Exit and fix manually and
Continue without these settings. The refused start still left a temp directory for a session id
with no transcript, a backups/ copy and a model-catalog refresh. With 1 the start ran, but the
sweep did not: .last-cleanup kept its earlier content and nothing under projects/,
file-history/, history.jsonl or the temp root was removed, since a one-day period cannot reach
files written that day. The Windows round therefore did not observe what the retention sweep
removes; the only sweep seen there is the housekeeping one that pruned backups/ and the temp root
(Scratch directories).
Observed on Windows 2.1.309, Linux 2.1.310 and macOS 2.1.310 for the user's other ways to end a session:
/clearis not a delete: it starts a new transcript file with the full session preamble, the old file keeps every turn and gains only acost-stateline, no marker, andhistory.jsonlkeeps the old session's lines and records the next prompt under the new id. On Linux the old file gained asystemline beside thecost-stateline, and the/clearcommand's lines were in the new file. On macOS the old file ended with itscost-stateline and nothing else, the new file opened with the/clearcommand's lines, and the temp root got a directory named by the new session id./exitremoves the process'ssessions/<pid>.jsonand.keyand its shell snapshot, and nothing of the transcript; with the daemon running, the worker holding the session and its registry file stay (Session daemon files). On Linux it also removed the version lock (Native installer) and the session'sbash-edit-diff/snapshots, and keptsession-env/<session id>/and the temp root (Scratch directories). On macOS it also removed the process's socket in/tmp/cc-socks/. Stopping a Remote Control server removedbridge-spawn/<cse id>/and the spawned session's process files, and leftbridge-pointer.json, the spawned session's transcript with itsccr-tip.jsonand the.claude.jsonkeys (Remote Control); quitting VS Code removedide/<port>.lockand thebash-edit-diff/snapshot (VS Code extension files)./logout(macOS 2.1.310) ended the process, removed.credentials.jsonand rewrote.claude.jsonwithoutoauthAccountandgithubWebConnectionStatusCache(claudeAiMcpEverConnectedstayed), while thebackups/copies kept the logged-out account'soauthAccount(Credentials).- Removing the project directory (
Remove-Item -Recurse -Force) removed only the project's files (Project-local files). Every trace in the home stayed: theprojects/<key>/directory with all eight transcripts,subagents/,tool-results/andcustom-title.json;file-history/<session id>/;plans/; everyhistory.jsonlline, each naming the project inproject; the.claude.jsonprojects.<path>entry andgithubRepoPaths; the CLI cache directory's<slug>/mcp-logs-*; and the temp root's<slug>directory. A later start in the home that stopped at the trust prompt wrote only a temp directory under the home's slug and a refresh ofplugins/synced/andskills/synced/. On Linux (rm -rf) the same held:projects/-home-sean-lab-proj/kept its 12 entries,.claude.jsonkeptprojects."/home/sean/lab/proj"andgithubRepoPaths, all 43history.jsonllines named the path, and the CLI cache and the temp root kept the project's slug,bash-edit-diff/included. On macOS (rm -rf, 62 entries) the same:projects/-Users-sean-lab-proj/kept its 19 entries,.claude.jsonkept the project's entry with itsmcpServers.fsandremoteControlSpawnMode, 58history.jsonllines named the path, andfile-history/,session-env/,plans/,paste-cache/,~/.config/git/ignore, the CLI cache's slug and the temp root's slug stayed. - A reboot removed nothing on Windows; on Linux and macOS it cleared the temp root, on macOS with
/tmp/cc-socks/, and changed nothing in the home (Scratch directories).
The per-prompt history (history.jsonl) usually outlives its transcript and is
then the only record of the prompt, except a prompt sent through claude agents or from the VS Code
extension's panel, which it does not record; the retention sweep keeps it, so a session found only
in history.jsonl is what a swept session leaves. Long pasted text is in paste-cache/, collected
since this round, and a short paste is inline in the history line. Beside it in the config
home sit the backups of files the agent edited (file-history/<session id>/), the dated copies of
.claude.json in backups/, the live-process records of sessions/<pid>.json, a background job's
copy of its parent transcript (jobs/<id>/parent-transcript.jsonl, not observed), the daemon's job
files and the project paths in .claude.json; the vendor's list above says which of them the
sweep removes and which it keeps, and a project's .claude.json entry goes with claude purge,
which the list does not name for the sweep. Inside
the transcript each api-request-blob line repeats a full request message, the prompt text
included (Record schema). Hermes Agent's state.db, a Cline session made by the
SDK's import and Pydantic AI's CLAI sessions.db hold copies that outlive the original file
(below). No deletion marker is stated or was seen. The analyzer reads history.jsonl as a secondary
source and the session records and .claude.json for their own rows, and paste-cache/ only to
expand a history line's paste placeholders
(Past-use rows and secondary sources), not
file-history/, backups/ or jobs/; it lists a removed project that the collected files still
name with the referenced status; the other agents' copies are read, if at all, under those agents
(see Secondary sources).
- Copies on Anthropic's servers. A session driven through a Remote Control server stayed listed on claude.ai, marked disconnected, after the local server stopped (macOS 2.1.310), and a cloud session's transcript is on the servers only (Remote Control, Cloud sessions).
- Copies held by other agents. Three agents copy these transcripts into stores of their own, where the copy outlives the original file (from source; none seen on a real install):
- Hermes Agent's
hermes_cli/foreign_sessions.pyreads Claude Code's~/.claude/projects/*/*.jsonland appends their user and assistant turns to itsstate.dbas a new session whoseorigin_json.imported_fromnames the tool, the source path and the foreign session id, stamped with the import time (hermes_cli/foreign_sessions.py:254-260; Imported sessions inhermes.md). - Cline's SDK session import makes an ordinary Cline session with a new id and
metadata.importedFrom{tool, sourceSessionId, sourcePath, importedAt, sourceProvider, sourceModel}; at the reviewed commit only Cline's example desktop app calls it (sdk/packages/core/src/services/session-import/service.ts:349-366; Transcript stores incline.md). - Pydantic AI's CLAI 2.0 (its command-line chat client)
/resumecopies them into itssessions.db(claude_code_sessions.py:41-43; Other agents' sessions inpydantic-clai.md). - Indexed by Zed. Zed's agent panel can run this agent as an external agent over ACP (the Agent
Client Protocol, the JSON-RPC protocol Zed uses to drive another agent); its threads sidebar then
lists the session in
sidebar_threadswithagent_idclaude-acpand the ACP session id insession_id, while the transcript stays in this agent's own store (agent_servers/src/custom.rs:17-19; Transcript stores inzed.md). Whether that id equals the session id this agent records was not determined.
Record schema¶
Every line is one JSON object with a type. The loader's own type table (cc:484009, nxr) lists
every type 2.1.289 reads, with how it merges repeats; a second table (cc:484010, rss) says which
survive a session copy. The Observed column counts lines on the WSL2 install; the Windows column
says whether the type was written on Windows 2.1.309:
type |
Loader class | Observed | Windows 2.1.309 | Holds |
|---|---|---|---|---|
user |
transcript | 6,008 | yes | a user turn: prompt, tool results, injected text |
assistant |
transcript | 9,911 | yes | one content block of a model response |
system |
transcript | 318 | yes | runtime notices, by subtype |
attachment |
transcript | 10,794 | yes | context injected into the next request, by attachment.type |
progress |
boundary-cleared | 0 | no | streamed tool, hook and forked-agent progress (bundle: cc:482374, cc:482655, cc:483757, cc:482596) |
file-history-snapshot, file-history-delta |
boundary-cleared | 172, 25 | yes | which files were backed up before edits, and the backup names (File-history records) |
last-prompt |
boundary-cleared | 602 | yes | lastPrompt (text of the latest prompt), leafUuid |
continued-in |
boundary-cleared | 0 | yes | continuedInSessionId, sessionId, timestamp (cc:480715): the session continued in another session's file (Session daemon records) |
content-replacement |
accumulate | 0 | no | replacements, optional agentId (cc:484029, cc:494705) |
api-request-shape, api-request-blob, api-request |
boundary-cleared | 0 | yes | request capture, written by default on 2.1.309: api-request-shape {shape, shapeHash, timestamp, version, sessionId}, the shape that api-request lines name by shapeHash, with a new line when the tool set changed (105,657 B for the first); api-request {base, cacheControl, id, keep, params, querySource, requestDigest, shapeHash, tail, timestamp, version} per request, params {betas, context_management, max_tokens, model, output_config, thinking}; api-request-blob {hash, message}, message {clear_at, content, output_config, role}, a full request message, so the prompt text is stored a second time in the file (a subagent's lacks clear_at) |
fork-context-ref, frame-link |
accumulate | 0 | no | fork reference (cc:484031); artifact link path, frameUrl, title, artifactCount, timestamp (cc:504346) |
summary |
last-wins | 0 | no | legacy session summary summary, leafUuid (cc:484055) |
custom-title, ai-title |
last-wins | 0, 548 | yes | customTitle (user-set name, {customTitle, sessionId, type}, written by /rename with custom-title.json beside it), aiTitle (generated name) |
tag, relocated |
last-wins | 0 | no | tag; relocatedCwd, written when the session's cwd moves (cc:484038) |
ended-by-model |
last-wins | 0 | no | timestamp only (cc:484055) |
agent-name, agent-color, agent-setting |
last-wins | 129, 0, 0 | agent-name only |
agentName, agentColor, agentSetting; agent-name is {agentName, sessionId, type} and was re-written each time the session was renamed |
pr-link |
last-wins | 221 | no (no pull request was made) | prNumber, prUrl, prRepository, timestamp |
mode, permission-mode |
last-wins | 608, 410 | yes | mode (normal observed); permissionMode (auto, plan observed; auto in all 308 records on the 2.1.296 install; auto, default, acceptEdits and plan on Windows 2.1.309, where the status line calls default "manual" and Shift+Tab cycles auto, manual, accept edits, plan). The permission mode is how tool calls are approved: default prompts, acceptEdits allows file edits, plan only plans, bypassPermissions allows everything, dontAsk denies what would prompt, auto lets a classifier decide (claude 2.1.296 darwin-arm64 @187461122 var CH=["acceptEdits","auto","bypassPermissions","default","dontAsk","plan"], with manual read as an alias for default; @188412753 if(e==="auto")return"classify";if(e==="bypassPermissions"\|\|_V(e,n))return"allow";if(e==="dontAsk")return"deny";return"ask"). The record is {type, permissionMode, sessionId} with no timestamp (@199803784 type:"permission-mode",permissionMode:this.currentSessionPermissionMode,sessionId:y). A fresh install starts in auto |
bridge-session |
last-wins | 256 | yes | bridgeSessionId, lastSequenceNum, ownerAccountUuid, ownerOrganizationUuid, sessionId; bridgeSessionId null on the line written at the disconnect (Remote Control) |
atis-latch, cost-state, queue-operation |
last-wins | 607, 25, 488 | yes | an opaque token; cumulative cost and per-model token usage, {hasUnknownModelCost, modelUsage, sessionId, startTime, totalAPIDuration, totalAPIDurationWithoutRetries, totalCostUSD, totalDuration, totalLinesAdded, totalLinesRemoved, totalToolDuration, type} on 2.1.309, written when a session's process stops writing to the file (an exit, a /clear, the daemon handover); prompt queue changes |
isolation-latch, dev-mods, memory-mode, worktree-state, history-suppression, attribution-snapshot, artifact-comment-monitor, artifact-autoreact-ledger, observer-ref |
various | 0 | no | session state (cc:484021, cc:484022) |
Session-state records (last-prompt, titles, agent-name, mode, permission-mode, atis-latch,
worktree-state, pr-link, frame-link, bridge-session) are re-appended as a block whenever the
session's metadata is flushed (cc:484021, cc:484022); on Windows 2.1.309 a /compact re-emitted
five of each. They carry sessionId and of the non-transcript types only pr-link, frame-link,
queue-operation, continued-in and the api-request* lines carry a timestamp, which for
pr-link is the time of the flush (timestamp:new Date().toISOString() in the re-append), not of
the pull request. Observed: one file holds 105 pr-link lines for a single URL.
On Linux 2.1.310 one /compact took ai-title from 6 to 16 lines, atis-latch from 8 to 18,
mode and permission-mode from 8 to 18, last-prompt from 8 to 17 and file-history-snapshot
from 10 to 21, so counts of these types are not turn counts. The permission-mode record is also
written lazily there: a Shift+Tab to default appeared as a permission-mode line only at the
next prompt, and a switch to acceptEdits never got one, while every prompt line carried its
permissionMode, which is therefore the per-turn evidence of the mode.
Byte shares on Windows 2.1.309, for the daemon worker's session file after the resume step
(1,271,056 B, 436 lines): attachment 91 lines and 531,967 B, api-request-blob 63 and 116,591 B,
assistant 49 and 105,885 B, user 51 and 98,449 B, api-request 36 and 64,636 B, system 25
and 13,389 B, file-history-snapshot 21 and 9,818 B; the three api-request-shape lines after the
/compact were 317 KB. Context and request capture, not the conversation, are most of the file.
On Linux 2.1.310 (2026-10-25 and 2026-10-26) every line type and key set of a two-turn session
equalled Windows 2.1.309's for the cli entrypoint, and a session that ran every step reached
1,202,662 B and 449 lines; a compaction took one file from 800 KB to 1,173,652 B, the context being
re-attached after the boundary. The key-set differences the round found are in the subsections
below.
On macOS 2.1.310 (2026-10-26) the line types and key sets of a turn matched Linux 2.1.310's, and the
session that ran most steps reached 1,332,231 B and 513 lines after two resumes, api-request-shape
lines holding 356,893 B of it and attachment lines 489 KB. A resumed session gained agent-name
and cost-state lines, and its custom-title line was re-appended at each resume. The
differences from Linux, a refusal with no permissionDecision, the auto_mode and
auto_mode_exit attachments, and the lines of Remote Control and teleported sessions, are in the
subsections below.
Transcript envelope¶
The envelope of user, assistant, system and attachment records (observed on every one unless
noted): type, uuid (UUID), parentUuid (UUID, or null on a chain root: observed on the first
transcript record of every file and on one system record), timestamp (ISO 8601 UTC,
milliseconds, Z; 27,061 of 27,061), sessionId (UUID), cwd, gitBranch, version (2.1.NNN),
isSidechain (false in session files, true in subagent files, 18,429 of 18,429), userType
(external), entrypoint (cli observed; claude-vscode on every line of a VS Code extension
session on Windows 2.1.309, VS Code extension sessions), optional
slug (plan name), agentId (subagent files only: equals the file's agent-<id>), and on some
user, assistant and attachment records a snake-case session_id
(Joins and timestamps). On Windows 2.1.309 the lines of a daemon worker's
file and of a subagent file also carry sessionKind (bg on every type) and slug. On Linux
2.1.310, with no daemon, no line carried sessionKind; slug appeared on every type once the
session had a title; subagent lines carry agentId on every type; and on a detached head
gitBranch is HEAD (Joins and timestamps). On macOS 2.1.310 the same,
and two values of entrypoint more: sdk-cli on every line of a session a Remote Control server
spawned (Remote Control), while the lines of a teleported session say cli, the
cloud session's replayed lines carrying remoteSourced (true) instead
(Cloud sessions).
assistant records¶
An assistant record adds message (the Messages API response object: id, type:"message",
role, model, content, stop_reason, stop_sequence, usage, container,
context_management, stop_details, diagnostics, input_transformations), requestId,
apiBlockIndex, effort, perTurnEffort, advisorModel, serverClassifierRequest, optional
thinkingDurationMs, requestedModel (observed on macOS with CLI 2.1.295, 2026-10-11),
attributionAgent, attributionSkill, and per tool call wireToolInputs and wireIngestContext
(Wire inputs) (observed; built at cc:482605). API failures are assistant records
with isApiErrorMessage:true, error, apiErrorStatus, message.model <synthetic> and one text
block holding the error (observed 10; cc:477488). On Windows 2.1.309 the key set adds requestRef,
session_id, thinkingDisplay and thinkingDurationMs (with thinking blocks), and
attributionMcpServer, attributionMcpTool and attributionSkill on the turn a skill ran
(/schedule); attributionAgent is on a subagent's lines. message.usage holds
cache_creation, cache_creation_input_tokens, cache_miss_reason, cache_read_input_tokens,
fallback_credit, inference_geo, input_tokens, iterations, output_tokens, output_tokens_details,
server_tool_use, service_tier, speed. On Linux 2.1.310 the 22-key assistant set was identical
to Windows 2.1.309's; wireIngestContext was on the Remote Control turn's lines,
attributionAgent on a subagent's, and the claude-sonnet-5-5 turns carried no thinking block
while the claude-opus-5-5 ones did. On macOS 2.1.310 the set was advisorModel, apiBlockIndex,
cwd, effort, entrypoint, gitBranch, isSidechain, message, parentUuid, perTurnEffort, requestId,
requestRef, requestedModel, serverClassifierRequest, sessionId, session_id, thinkingDisplay,
thinkingDurationMs, timestamp, type, userType, uuid, version, wireToolInputs, with
wireIngestContext from the first shell call on, slug once the session had a title,
attributionMcpServer and attributionMcpTool on an MCP call, and attributionAgent in a
subagent file; the lines of a session a Remote Control server spawned lacked requestRef,
serverClassifierRequest, session_id and slug. A start with no usable sign-in wrote the API
error form, with error and isApiErrorMessage.
user records¶
A user record adds message (role:"user", content string or block list), promptId (UUID),
and as applicable isMeta, origin{kind, ...}, promptSource, turnOrigin, turnPosition,
permissionMode, toolUseResult, sourceToolAssistantUUID, serverClassifierContext,
isCompactSummary, isVisibleInTranscriptOnly, summarizeMetadata, interruptedMessageId,
toolDenialKind, userFeedback, queueSkipAttachments, queueTranscriptOnly (the factory at
cc:483772 lists them; observed except the compaction fields, which Windows 2.1.309 showed). Windows
2.1.309 adds sandboxed (false), session_id, permissionDecision
(Permission decisions), mcpMeta (on the /install-github-app command's
lines), queueSkipSlashCommands (the Remote Control prompt), and on the prompt a /loop fired
scheduledFireId, scheduledTaskId, wakeupSource, turnCompanion and queuePriority. Values
seen there: promptSource typed, system (a task notification or a /loop firing),
suggestion_accepted (a prompt Claude suggested and the person accepted), queued (the Remote
Control prompt) and sdk (the VS Code extension); turnOrigin human, task_notification and
scheduled; origin.kind human and task-notification, with origin keys
kind, producer, runId on the latter; isMeta on the lines of a slash command. On Linux 2.1.310
the same keys and values, with these placements: sandboxed (false) only on the result line of an
executed shell call, absent from a denied call's and from other tools' lines;
serverClassifierContext absent while every call was approved by hand and present once a shell
call was allowed by configuration; mcpMeta on an MCP tool's result line; queueSkipAttachments
(true) on a prompt typed while a turn ran; queueSkipSlashCommands on the Remote Control prompt;
promptSource suggestion_accepted on the prompt the /schedule flow left; and permissionMode
default on a prompt typed in the mode the status line calls manual. On macOS 2.1.310:
promptSource queued on a prompt typed while an answer streamed, system on a background
command's completion notice and on a /loop firing, and sdk on a prompt from claude.ai to a
session a Remote Control server spawned, on a cloud prompt replayed by --teleport and on the VS
Code extension's prompt; permissionMode default on the claude.ai prompt and the replayed cloud
prompt, where local sessions started in auto; queueSkipAttachments on queued records;
isVisibleInTranscriptOnly on the compaction summary; and remoteSourced on the replayed cloud
lines (Cloud sessions). A refused call's result line carried toolDenialKind
and neither permissionDecision nor userFeedback (Permission decisions).
On macOS 2.1.310 a paste in a prompt's content is wrapped as
<pasted_content id="<4 hex>"> ... </pasted_content>, with the typed text after it, where the
history.jsonl line carries the paste without a wrapper, inline or by a placeholder
(history.jsonl).
Permission decisions¶
permissionDecision sits on the user line that carries the tool's tool_result (2.1.295 and
later). On Windows 2.1.309 and Linux 2.1.310 its keys were decision, source, reasonType,
grantScope and reasonCode, each only when it applies; a reasonCode cell names the Windows
values first and the Linux ones after the semicolon:
| How the call was allowed or refused | decision |
source |
reasonType |
grantScope |
reasonCode |
Other keys on the line |
|---|---|---|---|---|---|---|
the auto mode classifier allowed it; on Linux also an Agent call in default mode |
accept |
config |
classifier |
- | - | - |
the mode allows it without asking (a Read in default, an Edit in acceptEdits, a Grep) |
accept |
config |
mode |
- | - | - |
a shell command allowed without a prompt in default mode (a sleep and a cat, Linux) |
accept |
config |
subcommandResults |
- | - | - |
| the person answered Yes once | accept |
user_temporary |
- | once |
other, unknown or subcommandResults (a piped command approved as a whole); bashMiss:no-rule-match (a shell command no allow rule matched) or unknown (an Edit, a plain head) |
- |
| an allow-always rule the person had granted (Windows), or the allow-always answer itself (Linux) | accept |
user_permanent |
- | - | other; bashMiss:no-rule-match |
- |
| the person refused, typing a reason | reject |
user_reject |
- | - | other; bashMiss:no-rule-match |
toolDenialKind user-rejected, userFeedback (the typed reason) |
| the person declined a plan ("tell Claude what to do instead") | reject |
user_reject |
- | - | unknown |
toolDenialKind user-rejected, on the ExitPlanMode call |
| the person refused, with or without the dialog's amend option, or declined a plan (macOS 2.1.310) | no permissionDecision |
- | - | - | - | toolDenialKind user-rejected, no userFeedback; a reason is the next typed prompt |
| a rule given on the command line, on the cloud turn a teleported session replayed (macOS 2.1.310) | accept |
config |
rule |
- | - | ruleSource cliArg inside the decision |
One accept with reasonType other was also seen in plan mode, on both platforms. A file edit is
approved like a command. The shape of the denial is the one of
issue 97.
On macOS 2.1.310 (2026-10-26) the accepts took the forms of the table, with these details: an
accepted Edit in manual mode was user_temporary without grantScope; the allow-always answer's
own line was user_permanent with reasonCode bashMiss:no-rule-match and no grantScope; a
read-only shell command run without a dialog in manual mode was config with reasonType
subcommandResults and reasonCode unknown; an Edit in accept-edits mode was config with
reasonType mode; and the classifier's accept of an MCP call came with serverClassifierContext
on the line. Every refusal took the form with no decision record: the shell dialog's No, the same
answer after Tab had turned it into No, and tell Claude what to do differently, and option 3 of
the plan dialog each ended the turn at once with Interrupted · What should Claude do instead?,
wrote toolDenialKind user-rejected with no permissionDecision and no userFeedback, and took
the reason as the next prompt. Linux 2.1.310 wrote the user_reject form with userFeedback for a
refusal with a reason, so one build writes both forms. The dialogs offered, for a shell command,
1. Yes, 2. Yes, and don't ask again for: python *,
3. Yes, and switch to auto mode · auto mode handles these prompts for you and 4. No (footer
Esc to cancel · Tab to amend); for a file edit, 1. Yes,
2. Yes, and switch to accept edits (auto-approve file edits and common file commands) for this session (shift+tab)
and 3. No; and for a plan, 1. Yes, and use auto mode, 2. Yes, manually approve edits and
3. Tell Claude what to change.
system records¶
A system record adds subtype, isMeta (false on every builder, cc:483998, and on 318 of 318
observed), level, content and subtype fields. Observed subtypes: turn_duration (durationMs,
messageCount, pendingBackgroundAgentCount, no content), away_summary (content, a
model-written recap), local_command (content, level:"info", optional
commandRun{command, args}, usageReport), bridge_status (content, url), informational
(content, level:"notice"). The builders at cc:483998 add compact_boundary
(content:"Conversation compacted",
compactMetadata{trigger, preTokens, userContext, messagesSummarized, ...}, logicalParentUuid),
api_error (level: "error", error, retryInMs, retryAttempt, maxRetries, no content),
memory_saved (writtenPaths), agents_killed, stop_hook_summary, permission_retry,
scheduled_task_fire, cloud_session_status; cc:487124 adds model_refusal_fallback and others.
Observed on macOS with CLI 2.1.295 (2026-10-11), a stop_hook_summary record carries hasOutput,
hookAdditionalContext, hookCount, hookErrors, hookInfos, level, preventedContinuation,
stopReason and toolUseID beside the envelope. Many subtype literals in the bundle (init,
status, task_*, hook_*, ui_*) belong to the SDK stream, not the transcript.
Observed on Windows 2.1.309: turn_duration; away_summary, a recap written for a person who was
away, with content and the envelope; local_command with commandRun for /rename, /exit,
/clear and the Remote Control start and stop, the /clear one written in the new session's file;
compact_boundary with compactMetadata, content, level and logicalParentUuid;
bridge_status with url, the pairing URL; and scheduled_task_fire with cron, prompt and
taskId. pendingBackgroundAgentCount was on the system lines of the session that ran a
subagent.
Observed on Linux 2.1.310, the same subtypes. local_command lines (keys commandRun, content, cwd,
entrypoint, gitBranch, isMeta, isSidechain, level, parentUuid, sessionId, slug, subtype,
timestamp, type, userType, uuid, version) were written for /rename, /exit, /clear and the
Remote Control start and stop, and not for /model, /compact, /install-github-app, /loop or
/schedule, which left only their user command lines, nor for an /exit after a resume.
compact_boundary carries compactMetadata{cumulativeDroppedTokens, durationMs, postTokens,
preCompactDiscoveredTools, preTokens, preservedMessages{allUuids, uuids},
preservedSegment{headUuid, anchorUuid, tailUuid}, trigger} beside content, level and
logicalParentUuid (the shape tool's merged key list of the Linux round had put the three
segment uuids under preservedMessages; the macOS record read directly places them under
preservedSegment), and an
away_summary written for an idle gap carries the branch of the time it was computed.
pendingBackgroundAgentCount appeared on the system lines from the first backgrounded command.
Observed on macOS 2.1.310: compact_boundary with the envelope and compactMetadata, content,
level, logicalParentUuid, slug, subtype, compactMetadata holding trigger, preTokens, postTokens,
cumulativeDroppedTokens, durationMs, preCompactDiscoveredTools, preservedSegment{headUuid,
anchorUuid, tailUuid} and preservedMessages{allUuids, uuids};
scheduled_task_fire with content, cron, prompt, taskId; turn_duration with
pendingBackgroundAgentCount; and, on the cloud turn a teleported session replayed,
stop_hook_summary with the 2.1.295 keys, naming a hook of the cloud environment, and
informational with Session resumed (Cloud sessions).
attachment records¶
An attachment record adds attachment{type, ...} and, on most, rendered and renderedRole (the
text the model saw), and on some renderedBesideToolResult (observed on macOS with CLI 2.1.295,
2026-10-11). Observed attachment.type values: total_tokens_reminder, batching_reminder_sent,
deferred_tools_record, deferred_tools_delta, bash_output_audience_note, prompt_snapshot (the
system prompt and tool list), environment
(snapshot{workingDirectory, platform, shell, osVersion, isGitRepo, isWorktree, ...}),
skill_listing, queued_command, remote_session_change, date, model
(identity{modelId, marketingName, knowledgeCutoff}), agent_listing_delta,
mcp_instructions_delta, auto_mode, session_context (context{userEmail, gitStatus}),
credential_org (organizationUuid), edited_text_file (filename, snippet), instructions
(files), silent_turn_reminder, command_permissions, plan_mode, plan_mode_exit,
plan_mode_reentry, hook_system_message (hookName, hookEvent, content), thinking_drop,
read_truncation_notice. queued_command carries prompt, origin{kind}, commandMode,
source_uuid, delivery_id, timestamp: a prompt that arrived while a turn was running.
Key sets on Windows 2.1.309, where the shape tool gave them (none was read for a value):
attachment.type |
Keys |
|---|---|
auto_mode |
autoModeConsentFlow, bashFirst, bashFirstSteer, bypass, finishExposedWork, steerOnly: the auto-mode configuration of the turn, not a decision (issue 98) |
auto_mode_exit |
new type: written when the Shift+Tab cycle left auto; keys bashFirst, finishExposedWork, steerOnly (macOS 2.1.310) |
plan_mode |
isSubAgent, planExists, planFilePath, reminderType, type |
plan_mode_exit |
planExists, planFilePath, type; also written when the cycle passed through plan mode |
plan_file_reference, file |
new types: the plan and the files a /compact carried over |
environment |
snapshot in the CLI, changes and snapshot in the VS Code extension; snapshot holds additionalWorkingDirectories, isGitRepo, isWorktree, osVersion, platform, scratchpadDirectory, shell, workingDirectory |
model |
identity, text; a second one after a /model switch |
session_context |
context |
credential_org |
organizationUuid |
prompt_snapshot |
cliPrefix, contextRendering, echoWireToolInputs, inlineTools, keptReminders, reminderFold, systemPrompt, systemTurns, toolChangeHeader, tools |
deferred_tools_delta |
addedLines, addedNames, failedMcpServers, pendingMcpServers, readdedNames, removedNames, surfacedDefinitions, surfacedNames, wireHiddenNames, plus needsAuthMcpServers in the extension; it lists the connector servers |
skill_listing |
content, digests, isInitial, names, skillCount |
agent_listing_delta |
addedLines, addedTypes, builtInPluginTypes, builtInTypes, isInitial, removedTypes, showConcurrencyNote |
remote_session_change |
commit, managedCommit, managedPr, pr, url, sendUserFileHint: a link to a claude.ai session or a pull request when there is one; present in plain local sessions |
session_cron_carry |
tasks, type; by its name, the scheduled tasks carried into a new session |
task_reminder |
content, itemCount, type |
command_permissions |
allowedTools, type, on the /schedule skill's turn |
Also written there: total_tokens_reminder, batching_reminder_sent, bash_output_audience_note,
date, mcp_instructions_delta and deferred_tools_record. queued_command and
edited_text_file were not written: a prompt typed during a turn became an ordinary user line
(User-record kinds).
On Linux 2.1.310 the key sets above held, plan_mode with planExists false when the plan file
was not yet written, and the round added:
attachment.type |
Keys |
|---|---|
plan_mode_reentry |
planFilePath, type, at the next plan-mode turn after a plan was written (issue 98) |
compact_file_reference |
displayPath, filename, after a /compact |
plan_file_reference |
planContent, planFilePath: the plan re-injected after a /compact |
file |
content{file, type}, displayPath, filename: an edited file re-injected after a /compact |
deferred_tools_delta |
needsAuthMcpServers in the VS Code extension, as on Windows |
session_context |
in a subagent file, context with userEmail only (no gitStatus) |
A turn in auto mode that ran only a Read wrote no auto_mode, bash_output_audience_note or
batching_reminder_sent, which on Windows came with a shell call; a later auto session that used
an MCP tool wrote auto_mode. No auto_mode_exit was written when the Shift+Tab cycle left
auto.
On macOS 2.1.310 auto_mode came at the first turn, which ran only a Read, and auto_mode_exit
when the Shift+Tab cycle left auto, both unlike Linux (issue 98);
command_permissions{allowedTools} came with /loop; bash_output_audience_note and
batching_reminder_sent with a command the model ran in the background;
compact_file_reference, plan_file_reference and file after /compact; and no
edited_text_file for a file edit, which is recorded in toolUseResult and the file-history
lines. The environment attachment a teleported session replayed names the cloud machine's
scratchpad, /tmp/claude-0/-home-user-repo/<uuid>/scratchpad (Cloud sessions).
queue-operation records¶
A queue-operation record has operation (enqueue, dequeue, remove), timestamp,
sessionId, and on enqueue and remove the queued content; optional commandUuid,
deliveryId, reason (observed). On Windows 2.1.309 the keys were content, operation, sessionId,
timestamp, type: an enqueue and dequeue pair for a prompt typed while a turn ran, for the
Remote Control prompt, for a /loop firing and for a subagent's completion notice. On Linux
2.1.310 the CLI's lines had the same keys, and the VS Code extension's session wrote
operation, sessionId, timestamp, type, with no content. On macOS 2.1.310 the keys were
content, operation, sessionId, timestamp, type, in enqueue and dequeue pairs for a prompt
typed while an answer streamed (the input area showed ctrl+x ctrl+s to send now and
Press up to edit queued messages), for a background command's completion notice, for the prompt
from claude.ai and for each /loop firing.
Completeness and streaming¶
One API response becomes one assistant record per content block: all 9,911 observed assistant
records hold exactly one block, consecutive records share message.id and requestId, and only the
last carries a non-null stop_reason (observed; apiBlockIndex numbers them). There are no
streaming deltas to reassemble. The transcript is complete except for persisted tool output
(Persisted output) and file backups (file-history/); a paste is in the
prompt and again in history or paste-cache/ (Other stores).
Message content, tool calls and results¶
message.content blocks (observed counts):
| Record | Block type |
Fields |
|---|---|---|
assistant |
text (886) |
text |
assistant |
thinking (3,525) |
thinking, signature; thinking is "" in 3,165, text present in 360 |
assistant |
tool_use (5,513) |
id (toolu_...), name, input (object), caller{type:"direct"} |
assistant |
redacted_thinking, server_tool_use, others |
not observed |
user |
string content (474) | the prompt or injected text |
user |
text (35) |
text |
user |
tool_result (5,512) |
tool_use_id, content (string in 5,327; list of {type:"text", text} in 167 or {type:"tool_reference", tool_name} in 18), is_error (true 183, false 4,632, absent 749) |
user |
image |
not observed; text_of renders it [image] |
Tool call join¶
tool_use.id = tool_result.tool_use_id (observed). Each result's user record also carries
sourceToolAssistantUUID, the uuid of the assistant record holding the call, and
toolUseResult, the tool's structured result: for Bash stdout, stderr, interrupted,
isImage, noOutputExpected, optional returnCodeInterpretation, persistedOutputPath,
persistedOutputSize, bashEditDiff; for file tools filePath,
file{filePath, content, numLines, startLine, totalLines}, oldString, newString,
structuredPatch, originalFile, userModified; for web tools url, code, codeText, bytes,
result, durationMs, query, results; for Agent agentId, status, prompt,
resolvedModel, outputFile. Windows 2.1.309 adds: for PowerShell the Bash keys; for Read
file and type (text); for Write type create; for Agent canContinueAgent,
canReadOutputFile, description, isAsync; for ToolSearch content, matches, query,
total_deferred_tools, failed_mcp_servers; for Grep content, filenames, mode, numFiles,
numLines, totalLines; for CronCreate and CronDelete annotations, durable, humanSchedule,
id, nextFireAt, recurring; for AskUserQuestion answers, keyed by the question; and
backgroundTaskId when Claude Code moved a long command to the background, whose output then went
to the temp root's tasks/<id>.output (Scratch directories) and whose end
came back as a task-notification turn. Linux 2.1.310 showed the same keys, Agent adding
resolvedModel with the session's model, Bash {interrupted, isImage, noOutputExpected, stderr,
stdout, type}, and ToolSearch, which the model called to load a deferred tool (an MCP tool, a
cron tool) before using it, matches, query and total_deferred_tools. On macOS 2.1.310 the
Agent result held agentId, canContinueAgent, canReadOutputFile, content, description, isAsync,
outputFile, prompt, resolvedModel, status, and Edit's filePath, newString, oldString,
originalFile, replaceAll, structuredPatch, userModified. On an error it is the
error string (183 observed, all with
is_error:true). The model-visible text is the tool_result.content; toolUseResult repeats or
extends it.
Wire inputs¶
assistant.wireToolInputs maps each tool_use.id to the input exactly as the model sent it;
tool_use.input is the normalised input Claude Code ran (cc:482605 attaches both; cc:481832 and
cc:482596 compare and strip them). Observed: 1,607 of 5,513 differ. For 1,498 of 1,501 differing
Bash calls the wire command is the stored command with a leading cd <dir> && that the stored
input lacks, and wireIngestContext[<id>].cwd holds that directory; Edit adds replace_all,
ExitPlanMode and SendMessage add fields the model did not send.
Persisted output¶
When a result is too large, its full text goes to <session id>/tool-results/<name>.txt and the
tool_result.content holds a <persisted-output> stub with the path and a preview (cc:382991,
cc:474483); toolUseResult.persistedOutputPath names the file (37 observed) and
persistedOutputSize its size. On Windows 2.1.309 both keys were on the result line of a
20,000-line PowerShell listing and of a gh output, the files being
<session id>/tool-results/<9 base-36 chars>.txt (1.2 MB and 49.6 KB); the stub kept in the
transcript was about 38 KB. A 3,000-line listing the model only counted was not persisted. On Linux
2.1.310 the same two keys named tool-results/<9 base-36 chars>.txt for a 20,000-line head of a
word list and a gh output, and the stub was again about 37 KB. On macOS 2.1.310 a 3,000-line
head of the word list was persisted (31,286 B, mode 0644), and so was a gh output (51,455 B).
Tool names¶
Tool names observed: Bash, Read, Write, Edit, WebFetch, WebSearch, Agent,
SendMessage, SubagentHandback, ToolSearch, EnterPlanMode, ExitPlanMode; MCP tools are
mcp__<server>__<tool> (cc:489602; mcp__fs__list_directory on Windows 2.1.309). On Windows
2.1.309 the shell tool is PowerShell, and Bash, which runs Git Bash there, was used too (by the
model for ls and gh, and by the VS Code extension's session); also seen Read, Write, Edit,
Grep, Agent, ToolSearch, ExitPlanMode, AskUserQuestion, CronCreate and CronDelete.
On Linux 2.1.310 the shell tool is Bash, and a question about a file the Windows model answered
with a shell cat was answered with Read; also seen Write, Edit, Agent, ToolSearch,
ExitPlanMode, AskUserQuestion, CronCreate, CronDelete, Skill (for /loop and
/schedule) and mcp__fs__list_directory. On macOS 2.1.310 Bash, Read, Edit, Write,
Agent, ToolSearch, ExitPlanMode, CronCreate, CronDelete and mcp__fs__list_directory.
Input keys observed: command, description,
timeout, run_in_background (Bash); file_path, offset, limit (Read); file_path,
content (Write); file_path, old_string, new_string (Edit); url, prompt (WebFetch);
query (WebSearch); description, prompt, subagent_type, model, isolation (Agent).
User-record kinds¶
A user record is not always typed by the person:
| Signal | Meaning | Observed |
|---|---|---|
origin.kind:"human", promptSource typed, queued or suggestion_accepted |
a prompt the person submitted | 165 |
origin.kind:"human", promptSource sdk |
a prompt typed in the VS Code extension's panel | Windows 2.1.309 |
turnOrigin:"scheduled", promptSource:"system", with scheduledTaskId |
the prompt of a /loop firing, after a scheduled_task_fire system line |
Windows 2.1.309 |
entrypoint sdk-cli, origin.kind:"human", promptSource sdk |
a prompt sent from claude.ai to a session a Remote Control server spawned | macOS 2.1.310 |
remoteSourced:true, promptSource sdk |
a cloud session's prompt, replayed into a local session by --teleport |
macOS 2.1.310 |
no origin, parentUuid null, first line of a subagent file |
the task the parent agent gave the subagent | 80 of 80 subagent files |
origin.kind:"task-notification", content <task-notification>... |
a background task's completion notice; isMeta absent in 62 of 66; on Windows 2.1.309 with promptSource system and turnOrigin task_notification, for a subagent's end and a backgrounded command's |
66 |
origin.kind peer, coordinator, auto-continuation |
messages from other sessions or the runtime; isMeta:true |
65 |
isMeta:true, <system-reminder> or <local-command-caveat> |
injected context | 87 strings, 27 blocks |
<command-name>, <command-message>, <local-command-stdout> |
a slash command and its output; isMeta absent; the 27 <command-message> records also carry origin.kind:"human" and are counted above |
38 |
isCompactSummary:true |
the summary that replaces history after compaction, text starting "This session is being continued from a previous conversation" (cc:482588); built through the user-message factory without isMeta (cc:482593, cc:482597, cc:482598); with isVisibleInTranscriptOnly on Windows 2.1.309 |
Windows 2.1.309 |
tool_result blocks |
tool output | 5,512 |
A prompt typed while a turn runs is first a queue-operation enqueue with its content, then
either a queued_command attachment (delivered inside the running turn) or a later user record
with promptSource:"queued". Observed: 11 queued_command attachments with origin.kind:"human"
whose text is in no user record of the file. On Windows 2.1.309 a prompt typed while a command ran
was an enqueue and dequeue pair and then an ordinary user line with promptSource typed,
with no queued_command attachment; the Remote Control prompt from the phone was the line with
promptSource queued (Remote Control). Linux 2.1.310 confirmed both forms
and the task-notification turns, one per backgrounded command (user records). On
macOS 2.1.310 a prompt typed while an answer streamed was an enqueue and dequeue pair and then
a user line with promptSource queued.
Joins and timestamps¶
- Timestamps: per record
timestamp, ISO 8601 UTC with milliseconds andZ(observed 27,061 of 27,061;new Date().toISOString()in every builder). Not monotonic in file order: in 52 of 95 files a record is older than an earlier line, mostly by under a second (assistant blocks and attachments written after the fact), 19 by a minute or more. Session-state records have no timestamp. - Session id:
sessionIdequals the file name in all session-file records (12,526 of 12,526). In a subagent filesessionIdis the parent session's id (18,429 of 18,429) andagentIdnames the subagent. The snake-casesession_idon some records equalssessionIdin 12 session files and in 3 names an earlier session whose file exists while none of those records'uuids are in it; consistent with a resumed or forked session, purpose not determined. On Windows 2.1.309claude --resumeappended to the chosen session's own file under the samesessionId(no new file and no copy), while the daemon handover copied the console session's turns into the worker's new file, so the worker's file holds lines first written under the console's session; whether those copies are the records whosesession_idnames an earlier session was not determined (values were not compared). On Linux 2.1.310, with no daemon,claude --resumeandclaude --continueboth appended to the session's own file, and their prompts tohistory.jsonl, under the samesessionId, with nocontinued-inline; each resuming process got its own temp-root directory under a new uuid (Scratch directories). On macOS 2.1.310 the same: after a--resumeand a--continuethe file held onesessionIdvalue; the resumed session came back inautomode although it had been left indefault; and since the resuming process's temp-root uuid is in no transcript, registry or history line, a scratchpad cannot be joined to its transcript by name after a resume. A session a Remote Control server spawned and a teleported cloud session have files of their own; the cloud session's id (session_01<22 chars>) is in the teleported file only inside URL and text values (Cloud sessions). - Daemon handover: the console file's
continued-incontinuedInSessionIdis the worker's session id, the worker'sjobs/<short>/state.jsonresumeSessionIdis the console's, and the console'ssessions/<pid>.jsoncarries the job asparkedJobId(Session daemon records). Later turns, an attach throughclaude agentsand a--resumeall land in the worker's file. While a worker holds the last session,claude --continueis refused withYour most recent conversation is running in the background (session <uuid>). Use `claude agents` to find and attach to it, or `claude --resume` to pick another session.On Linux 2.1.310 no worker held a session and--continueresumed the latest one in place, so the refusal is the daemon's behaviour, not a rule of--continue. /clear: a new session id and file in the same process; the old file is not linked to the new one by any record seen (its last line is acost-state). On macOS 2.1.310 the new file opens with the/clearcommand's lines, andhistory.jsonlrecords the prompts before and after it under the two ids.- Project path:
cwdon every transcript record; can change within a session (4 of 15 session files had more than one) andrelocatedrecords it (cc:484038); the directory<key>is lossy (Where the project path is recorded). - Branch:
gitBranchon every transcript record (never empty observed); changes within a session (2 files). On a detached head it is the literal stringHEAD, never empty and never a commit hash (Linux 2.1.310, agit checkout --detachbefore one prompt: four of the turn's five envelope lines carriedHEAD, and the fifth, anaway_summarycomputed before the checkout,main; macOS 2.1.310, every line of the detached-head turn). - Model:
message.modelonassistantrecords (claude-<family>-N-Nobserved,<synthetic>for local errors);requestedModel, the model the request asked for (claude 2.1.296 darwin-arm64 @198072824requestedModel:X.model), equal tomessage.modelon every 2.1.296 line observed;modelattachmentidentity.modelId;cost-state.modelUsagekeys. A/modelswitch is auserline<command-name>/model</command-name>followed by auserline with the command output, built from@199593943var fle="Set model to ",gne=" and saved as your default for new sessions"; observed on 2.1.296 as<local-command-stdout>Set model to `<display name>` and saved as your default for new sessions</local-command-stdout>. The parser keeps it ascommand output:and marks the change itself frommessage.model. On Windows 2.1.309 a/modelswitch to Sonnet saved as the default leftmessage.modelandrequestedModelboth the resolved id (claude-sonnet-5-5) on the following lines, the earlier ones stayingclaude-fable-5-1; the alias the person picked is in no transcript line, only insettings.jsonmodel(sonnet), and a secondmodelattachment followed. A subagent inherited the session's model, and later sessions, the VS Code extension's included, started on the saved default. On Linux 2.1.310 the default before any switch wasclaude-sonnet-5-5, where the Windows cell's wasclaude-fable-5-1: the model catalog'sstate.selection_sourcewasuser_setting, so the default is account state, not a property of the build. Picking Opus with/modelsaved the aliasopustosettings.jsonmodelat once, with no option to decline (2.1.309 asked), and left twouserlines (the command and a<local-command-stdout>withSet model to); the nextassistantlines carriedclaude-opus-5-5in bothmessage.modelandrequestedModel, with a secondmodelattachment (identity{knowledgeCutoff, marketingName, modelId}, text). A subagent again inherited the session's model. On macOS 2.1.310 the default before any switch wasclaude-opus-5-5, the account's again./modellisted1. Default (recommended) Opus 5.5,2. Opus 5.5,3. Fable 5.1and+10 modelswith an effort line, and picking Fable opened a confirmation,Switch model?, sayingYour next response will be slower and use more tokensand that the cached conversation would be re-read, after which the output wasSet model to `Fable 5.1` and saved as your default for new sessions;settings.jsongot the aliasfable, the model catalog was rewritten, and the nextassistantline carriedclaude-fable-5-1in both keys, with a secondmodelattachment and a newapi-request-shape. A subagent run after the switch reportedresolvedModelclaude-opus-5-5, the account default rather than the session's model. The VS Code extension's start rewrotesettings.jsonmodeltoopuswith no command, so a change of that key is not always a/model. - Subagents: the parent's
Agenttool_use.idequals the subagent's.meta.jsontoolUseId, and the parent'stoolUseResult.agentIdequals the file's agent id (76 of 76 non-fork subagents). Forked agents (agentType:"fork", 4) have atoolUseIdthat is not in the parent transcript. Confirmed on Windows 2.1.309 and on Linux and macOS 2.1.310, where every subagent line carriesagentIdand the parent's onlyagentIdis on theAgentresult line. - Tool results:
sourceToolAssistantUUIDon the result line is theuuidof theassistantline with the call (observed on Windows 2.1.309 as on WSL2). A backgrounded command's result carriestoolUseResult.backgroundTaskId; its output went to a temp-roottasks/<id>.outputfile and its end came back as atask-notificationturn (whether the id is the file's name was not compared). - Turn:
promptIdgroups a prompt's records;turnPosition{ promptIndex, turnIndex}on someuserrecords.
Other stores¶
sessions/<pid>.json and the .meta.json sidecars: Transcript stores. No
SQLite store holds transcript content. .claude.json projects keys are project paths
(Where the project path is recorded).
history.jsonl¶
.claude/history.jsonl (observed 162 of 162):
{display, pastedContents, timestamp, project, sessionId}, timestamp epoch milliseconds
(integer), project the cwd. Written by addEntry (cc:479374) and read with the schema at
cc:479372. pastedContents maps a paste number to {id, type, content, mediaType, filename} when
the paste is at most 1,024 characters, else to {id, type, contentHash, mediaType, filename} with
the text stored in paste-cache/ under its hash (cc:479369 z8=1024, cc:479374
registerPastedTextForRecall, cc:479360 paste-cache); display holds the [Pasted text #N]
placeholder. Every pastedContents on the WSL2 install was empty. On Windows 2.1.309 a pasted
text of about 40 lines (shown as [Pasted text #1 +N lines]) gave pastedContents
{"1": {contentHash, id, type}}, with no content, mediaType or filename, and the text went to
paste-cache/<contentHash>.txt (6,604 B); the transcript's user line holds the paste inline, so
the cache is a second copy while the transcript exists and the only one after. paste-cache/ is
collected since this round (Exclusions); what the history outlives is under
What survives a delete.
On Linux 2.1.310 both forms were written to one file. A short multi-line paste (shown as
[Pasted text #1 +N lines]) gave pastedContents {"1": {content, id, type}}, the text itself
in content and no paste-cache/ file, so the history line holds the paste and outlives the
transcript with it; a long one (300 lines of a manual page) gave {"2": {contentHash, id, type}}
and paste-cache/<contentHash>.txt. The key is the paste's number within the session, not always
1. On macOS 2.1.310 a 269-character paste of four lines took the inline content form and a
2,416-character one of 19 lines the contentHash form with its paste-cache/ file, so the cut is
at the 1,024 characters of the bundle; display held the [Pasted text #N +M lines] placeholder
and the typed text, where the transcript's prompt holds the paste expanded and wrapped
(user records).
On Windows 2.1.309 every typed prompt and slash command was a line (/model, /compact,
/rename, /exit, /install-github-app), a mistyped subcommand (claude ls) became a prompt
and a line, a session with no prompt left one line, and a refused or trust-prompt start left none.
A prompt sent through a console attached with claude agents was not recorded, so the transcript
is its only record. On Linux 2.1.310 the same held except that /model was not a line, while
/compact, /rename, /exit, /clear and the prompts after --resume and --continue were; a
prompt sent from the VS Code extension's panel was not a line either, and the retention sweep left
the line of the session whose transcript it deleted. On macOS 2.1.310 every slash command was a
line, /model included, with the command as typed in display (/rename lab-round,
/loop 1m echo hi), while the transcript records the same command in a user line wrapped in
<command-name> tags (User-record kinds).
File-history records¶
file-history-snapshot and file-history-delta records name the files backed up before each edit
(snapshot.trackedFileBackups keyed by path,
backup{backupFileName, backupTime, version, realParentDir}); the bytes are in
.claude/file-history/<session id>/. The paths in trackedFileBackups are files the agent edited,
which no other record lists in one place.
On Windows 2.1.309 a file-history-delta line has backup, messageId, snapshotMessageId, timestamp,
trackingPath, type. The backups are file-history/<session id>/<16 hex>@v<n>, one <16 hex> per
tracked path and a new @v<n> per version: the first edit wrote @v1, the pre-edit copy, whose
modification time was the source file's own; a later edit wrote @v2; and the next prompt after an
edit wrote the post-edit copy as @v3, so a snapshot is taken at the next prompt too. The plan
file the Write tool wrote in plan mode was tracked the same way. The empty directory is created
at the session's start.
On Linux 2.1.310 the directory was created at the first edit, and the pre-edit @v1 copy's
modification time was the time of the backup, not the source file's, so whether a backup keeps
the source's time differs between the platforms. A new @v<n> was written at every prompt while a
file was tracked, a prompt that edited nothing included, while the transcript held one
file-history-delta line per tracked file. The plan file written in plan mode was tracked from the
next prompt, its first copy @v2 with no @v1, since the Write that created it had no earlier
content. On macOS 2.1.310 the same: the directory came with the first edit's pre-edit @v1 (190 B,
mode 0644), the next prompt wrote @v2 with no edit in its turn, and the plan file got a hash of
its own, its first copy @v2 (1.4 KB); the file-history-delta keys were the Windows set.
Permission configuration¶
- Key names come from the 2.1.296 binary and were observed by name on the 2.1.296 install, values never read (issue 93).
- Settings files:
<config home>/settings.jsonand<config home>/settings.local.jsonfor the user, and<project>/.claude/settings.json(shared, committed) and<project>/.claude/settings.local.json(personal, gitignored) per project (@188743274case"projectSettings":return Se(".claude","settings.json");case"localSettings":return Se(".claude","settings.local.json"); withCLAUDE_CONFIG_DIRset both the global config and the user settings move into it,@212229478globalConfig:it(e||zt(),".claude.json"),userSettings:it(e||it(zt(),".claude"),"settings.json")). - Their
permissionsobject,@188532304function ia(e){return u({allow:A(Gi()).optional().describe("List of permission rules for allowed operations"),deny:A(Fo()).optional()...,ask:A(Fo()).optional()...,defaultMode:pa(rh,U([...CH,...Ho(e)])).optional()...,disableBypassPermissionsMode:U(["disable"]).optional()...,blockReadsOutsideWorkingDirectories:H().optional()...,additionalDirectories:A(o()).optional(), holdsallow,denyandask(rule strings, a tool name with an optional argument pattern in parentheses, such asBash(npm test:*)),defaultMode(a mode name of thepermission-moderow in Record schema),disableBypassPermissionsMode(only"disable"; also a managed-settings key,@188373224{path:["permissions","disableBypassPermissionsMode"],restrictive:"disable"}),blockReadsOutsideWorkingDirectories(boolean) andadditionalDirectories(paths outside the working directory the agent may use). "Always allow" answers to a prompt are persisted into these lists and a mode chosen to keep intodefaultMode(@191956974permissions:{defaultMode:e.mode}). The same files hold keys that can carry credentials, which the parser never reads (Secrets). - In
~/.claude.json(the global config, a credential file, Credentials) eachprojects.<path>object starts from@189922165var bue={allowedTools:[],mcpContextUris:[],mcpServers:{},enabledMcpjsonServers:[],disabledMcpjsonServers:[],hasTrustDialogAccepted:!1,hasClaudeMdExternalIncludesApproved:!1,:allowedTools(tools allowed for that project),hasTrustDialogAccepted(the person accepted the folder trust prompt),hasClaudeMdExternalIncludesApproved(the person approvedCLAUDE.mdimports from outside the project),enabledMcpjsonServersanddisabledMcpjsonServers(approval of the project's.mcp.jsonservers) andmcpServers(project-scoped server definitions, which can carry headers). The parser reads the rows of Permission rows; the MCP lists and definitions are not read. - Observed on Windows 2.1.309: the allow-always answer, worded "Yes, and don't ask again for :
python calc.py 1 1" (the prefix slot was empty, so the grant was the exact command), wrote the
project's
.claude/settings.local.jsonwithpermissions.allowholding one rule,PowerShell(python calc.py 1 1), the rule form naming the Windows shell tool where the Linux one namesBash(...). It did not write the usersettings.local.jsonorprojects.<path>.allowedTools, which stayed empty. Its next use is recorded assourceuser_permanent(Permission decisions). No mode was kept as a default in the round, sodefaultModewas not written; the/modeldefault went to the usersettings.jsonmodel(Files that hold evidence). - Observed on Linux 2.1.310: the allow-always option offered a wildcard prefix rule, worded
"Always allow python3 *", for the command
python3 calc.py 1 1, and wrote the project's.claude/settings.local.jsonwithpermissions.allowholding one 15-character rule, the length ofBash(python3 *)(the value was not read). It did not write the usersettings.local.json, the usersettings.jsonorprojects.<path>.allowedTools, and the answer's own result line was recorded assourceuser_permanent(Permission decisions). Again no mode was kept as a default. - Observed on macOS 2.1.310: the allow-always option read
Yes, and don't ask again for: python3 *and wrote the project'ssettings.local.json(72 B) with one 15-character rule, again the length ofBash(python3 *), and nothing in the user files orallowedTools. A shell read of a file outside the project offered a directory-scoped grant instead,Yes, allow reading from /usr/share/dict from this project, whose settings form was not exercised. A fresh install starts inauto, the Shift+Tab cycle runs auto, manual, accept edits, plan, and a resumed session comes back inauto. No mode was kept as a default.
.claude.json¶
The keys seen on Windows 2.1.309, by name and type (the round read no value). After the installer
(558 B): autoUpdates, autoUpdatesProtectedForNative, changelogLastFetched, firstStartTime,
firstStartVersion, hasResetAutoModeOptInForDefaultOffer, installMethod (native),
machineID (64 hex), migrationVersion, opusProMigrationComplete, seenNotifications,
sonnet1m45MigrationComplete, userID (64 hex). After the sign-in (63,377 B): oauthAccount
(Secrets), projects, githubRepoPaths
(Where the project path is recorded),
claudeAiMcpEverConnected (one entry), pluginUsage.<plugin>@builtin{lastUsedAt, usageCount,
lastUsedNumStartups}, cachedExperimentData, cachedGrowthBookFeatures (about 50 KB of
feature-flag cache), clientDataCacheSlots.bi1-<16 hex>{at, data, entrypoint, model, org} (the
default model and the surface, cli or claude-vscode, before any prompt), numStartups,
hasCompletedOnboarding and officialMarketplaceAutoInstalled. Later starts added agentLastUsed,
announcementImpressions, cachedArtifactRoster{accountUuid, capabilities, fetchedAt,
organizationUuid, version}, fullscreenUpsellSeenCount, hasOpenedAgentsView, lastPlanModeUse,
remoteControlUpsellSeenCount, tipLifetimeShownCounts, tipsHistory, the Remote Control keys of
Remote Control and skillUsage.<skill>{lastUsedAt, usageCount} (loop and
schedule after those skills ran).
Each projects.<path> object had, beside the keys of
Permission configuration, hasClaudeMdExternalIncludesWarningShown,
hasUnseenTeamArtifacts, lastGracefulShutdown, lastVersionBase, an mcpServers entry per
local-scope MCP server (MCP state), and after the first turn the last session's
metrics: lastAPIDuration, lastAPIDurationWithoutRetries, lastCost, lastDuration,
lastLinesAdded, lastLinesRemoved, lastModelUsage, lastSessionId, lastSessionMetrics,
lastStartTime, lastToolDuration, lastTotalCacheCreationInputTokens,
lastTotalCacheReadInputTokens, lastTotalInputTokens, lastTotalOutputTokens and
lastTotalWebSearchRequests. The file is rewritten at every start and exit, with a dated copy in
backups/ (Files that hold evidence), and keeps a removed project's
entry (What survives a delete).
On Linux 2.1.310 the installer wrote 517 B with twelve keys, the Windows set less
changelogLastFetched, which the first launch added. After the sign-in the file was 65,833 B with
43 top-level keys, all within the Windows set; oauthAccount had 20 keys
(Secrets), cachedGrowthBookFeatures was about 60 KB, cachedExperimentData held 15
tengu_* experiments, pluginUsage four @builtin plugins, and clientDataCacheSlots
data{cedar_basin, cedar_lagoon{claude-fable, claude-mythos}}; also present were
passesEligibilityCache.<uuid>, groveConfigCache.<uuid>, penguinModeOrgEnabled and
hasSeenAutoDefaultNotice. Later starts added apiCreditLinkStatusCache, promptQueueUseCount,
feedbackSurveyState{lastShownTime}, tipsHistoryByCommand.<command>{numStartups, tipId},
tipLifetimeShownCounts.<tip> (14 tips, among them install-github-app, install-slack-app and
prompt-queue) and githubWebConnectionStatusCache
(Source-host integration), 55 top-level keys by the end. A project
entry's metrics added lastFpsAverage, lastFpsLow1Pct and, in lastSessionMetrics,
frame_duration_ms_{avg,count,max,min,p50,p95,p99}. The file was also rewritten, with a backup,
in a session left idle overnight (Files that hold evidence).
On macOS 2.1.310 the installer wrote the twelve Linux keys (517 B). The sign-in made it 2,218 B,
adding oauthAccount (Secrets), clientDataCacheSlots.<slot id>{at, data, entrypoint,
model, org} (entrypoint cli), additionalModelOptionsCache[{description, label, value}],
additionalModelOptionsAnsweredAt, additionalModelCostsCache, autoCompactWindowsCache,
claudeCodeFirstTokenDate, modelAccessCache and orgModelDefaultCache. The first session start
made it 65,874 B with 44 top-level keys, adding the Linux set (apiCreditLinkStatusCache,
cachedExperimentData with 14 tengu_* experiments, cachedExperimentFeatures,
cachedExtraUsageDisabledReason, cachedGrowthBookFeatures and cachedGrowthBookFeaturesAt,
claudeAiMcpEverConnected, closedIssuesLastChecked, githubRepoPaths, groveConfigCache,
hasCompletedOnboarding, hasSeenAutoDefaultNotice, lastOnboardingVersion,
lastReleaseNotesSeen, numStartups, officialMarketplaceAutoInstallAttempted,
officialMarketplaceAutoInstalled, passesEligibilityCache, penguinModeOrgEnabled,
pluginUsage, projects); the project entry had eleven keys at the trust dialog. The second start
added announcementImpressions, cachedArtifactRoster, fullscreenUpsellSeenCount,
tipLifetimeShownCounts and tipsHistory (49 keys), and later steps remoteDialogSeen,
lastPlanModeUse, promptQueueUseCount, tipsHistoryByCommand, remoteControlMachineId (a uuid)
and hasRemoteEnvironment (56 keys), with remoteControlSpawnMode and the per-session usage
counters (lastAPIDuration, lastCost, lastFpsAverage, lastLinesAdded and the rest) in the
project entry (Remote Control, Cloud sessions).
githubWebConnectionStatusCache came with the account's GitHub connection
(Source-host integration). /logout removed oauthAccount and
githubWebConnectionStatusCache and left the rest
(What survives a delete).
Session daemon records¶
The files are listed under Session daemon files. Key sets on Windows 2.1.309:
daemon.lock:jsonPath, launchTarget, logPath, origin, pid, procStartFt, processWrapper, spawnedBy{cwd, label, pid}, startedAt, version.daemon.status.json:supervisorPid, supervisorProcStart, workers{}, writtenAt.daemon/roster.json:proto, supervisorPid, updatedAt, workers{<short>: {attempt, cliVersion, cwd, decModes, dispatch, firedInteractiveMarks, pid, procStart, ptyAuth, ptySock, rendezvousSock, rvAuth, sessionId, startedAt}}, wheredispatchholdsagent,cols,createdAt,cwd,env{CLAUDE_BG_ISOLATION},isolation,launch{args or flagArgs, fork, mode, restoresTranscript, sessionId},nonce,proto,respawnFlags,rows,seed{intent},sessionId,shortandsource;ptyAuthandrvAuthare 32-hex tokens. On Linux 2.1.310, with no worker,proto, supervisorPid, updatedAt, workers{}.daemon/attach-journal/<uuid>.json:attachCold, attachMs, attempt, gestureId, interactiveReached, marksExpected, msgsInJsonl, msgsLoaded, msgsRenderedAtFirstPaint, pid, procStart, startedAtEpochMs, surface, via, one per attach, removed when the console exits.jobs/<short>/state.json:backend, bgIsolation, children, cliVersion, createdAt, cwd, daemonShort, detail, firstTerminalAt, inFlight{drainableMonitors, kinds, queued, tasks}, intent, interactiveLineage, lastTerminalAt, linkScanOffset, linkScanPath, name, nameSource, needs, output{result}, providerEnv, respawnFlags, resumeSessionId, sessionId, state, template, tempo, tokens, updatedAt;namefollows/rename, andresumeSessionIdnames the console session the job took over.jobs/<short>/timeline.jsonllines are{at, detail, state, text}.jobs/.draft-<8 hex>:{collapsed, q, ts}.sessions/<pid>.jsonof the console gainedparkedJobId, and each worker's hasjobIdand a two-characterkindin place ofinteractive; the idle spare worker's hasspare(removed the moment a prompt was sent), and one hadagent. After/renamethe worker'snamechanged and aformerNameslist of objects appeared.
The handover itself is in the transcripts: 36 seconds after the start, the console's file got a
continued-in line and a closing cost-state, and the worker's new file started with an
agent-name and an ai-title line, then received the console's turns and every later one, its
lines carrying sessionKind bg (Transcript envelope). The request
capture lines are per process and were not copied. What triggered the handover is not known: the
only actions before it were one prompt and the Shift+Tab mode cycle. The console's file was still
rewritten at its exit, with the same turns.
MCP state¶
On Windows 2.1.309 claude mcp add --scope local put the server under
.claude.json projects.<path>.mcpServers.<name>{args, command, env, type}, not in the top-level
mcpServers; the first start that used it wrote state/mcp-discover-verdicts.json
({"stdio:<hex>": {modernProtocolVersion, observedAt}}, one key per stdio server probed), and each
server start wrote a log file in the CLI cache directory. Its tool calls are
mcp__<server>__<tool> (Tool names). Whether claude mcp add wrote
.claude.json before the session started was not separated there. The account's claude.ai
connectors need no local configuration and log on every start.
On Linux 2.1.310 claude mcp add --scope local fs -- npx -y @modelcontextprotocol/server-filesystem
<project> wrote .claude.json itself, before any session, and said so:
Added stdio MCP server fs with command: ... to local config and
File modified: /home/sean/.claude.json [project: /home/sean/lab/proj]. The server went to
projects.<path>.mcpServers.fs{args, command, env, type} (command npx, type stdio, an
empty env{}), with no top-level mcpServers and no .mcp.json. In the next session the model
loaded the server's tool with ToolSearch and called mcp__fs__list_directory, both allowed by
configuration in auto mode, the result line carrying mcpMeta. state/mcp-discover-verdicts.json
(101 B, mode 0600) had been written at the second start, before any local server existed, with one
key of the form mcpsrv_<24 chars> ({modernProtocolVersion, observedAt}), the claude.ai
connectors' id form, and gained no key for the stdio server.
On macOS 2.1.310 claude mcp add -s local fs -- npx -y @modelcontextprotocol/server-filesystem
<project> printed the Linux lines, naming /Users/sean/.claude.json, and wrote
projects.<path>.mcpServers.fs{args, command, env, type} (three args, command npx, an empty
env, type stdio), with no .mcp.json in the project or the home; claude mcp list printed
Checking MCP server health and logged every server (CLI cache directory).
In the next session, in auto mode, the model loaded the tool with ToolSearch and called
mcp__fs__list_directory with no dialog, the classifier allowing it; the assistant line carried
attributionMcpServer and attributionMcpTool, the result line mcpMeta.
mcp-needs-auth-cache.json
was not written, the server needing no sign-in. state/mcp-discover-verdicts.json (101 B, the
mcpsrv_ key form) was written at the first session start.
Scheduled tasks¶
On Windows 2.1.309 /loop 1m <prompt> is run through tools: a CronCreate call starts it and a
CronDelete call stops it (their result keys are under Tool call join), each
firing is a system scheduled_task_fire line followed by a user prompt with turnOrigin
scheduled (User-record kinds), and the project gets
.claude/scheduled_tasks.lock and a rewritten .git/info/exclude
(Project-local files); .claude.json counts the skill in skillUsage.
/schedule, for cloud routines, ran as a skill that asked with AskUserQuestion and, cancelled,
created nothing locally: no routines/ directory in the project or the config home, no jobs/ or
systemd/ entry.
On Linux 2.1.310 the record set was the same: CronCreate and CronDelete calls (both loaded with
ToolSearch), two scheduled_task_fire lines and two user prompts with turnOrigin
scheduled, promptSource system and the scheduled-fire keys, Skill tool calls for both
commands, no local_command line, and skillUsage.loop and skillUsage.schedule in
.claude.json. The project's .claude/scheduled_tasks.lock was written at the loop's start and
left behind after the stop (Project-local files). The cancelled
/schedule again created no routine, and its AskUserQuestion left a prompt line with
promptSource suggestion_accepted.
On macOS 2.1.310 /loop 1m echo hi answered with the job's terms
(Expiry: recurring jobs auto-expire after 7 days,
Runs until you close this session · For durable cloud-based loops, use /schedule) and the same
record set: a CronCreate call after a ToolSearch, three scheduled_task_fire lines, each
followed by a user line with turnOrigin scheduled, promptSource system, scheduledFireId,
scheduledTaskId, wakeupSource, queuePriority, queueSkipSlashCommands and turnCompanion
and a queue-operation pair, the firing's Bash call allowed by the classifier (the screen read
Allowed by auto mode classifier), a command_permissions{allowedTools} attachment, and a
CronDelete call for "Stop the loop.". .claude/scheduled_tasks.lock lived from the /loop to
the /exit (Project-local files); no scheduled_tasks.json, routines/,
jobs/ or systemd/ was written, so the loop is held by the session and ends with it. /schedule
was not run.
Source-host integration¶
Observed on Windows 2.1.309, with the GitHub CLI gh installed and no GitHub sign-in at first:
githubRepoPathsin.claude.jsonmaps every GitHub repository the CLI has seen as a checkout'soriginremote to its local checkouts, filled at the first launch from the git remote alone (Where the project path is recorded); it is the list of repositories the host has worked on, not of what an account may reach./install-github-appasked forgh auth loginfirst, then installed the Claude GitHub app; the repositories the app may reach are chosen on GitHub's website (all by default) and live on GitHub. Claude Code's own state recorded nothing of it: nogh-pr-status-cache.json, no new.claude.jsonkey, no new file under~/.claude; the transcript gained the command's lines.- The connected account is in
gh's own store,%APPDATA%\GitHub CLI\config.ymlandhosts.yml(which names the user), with the token in the Windows Credential Manager. These aregh's files, attributed to thesharedcatalog agent and flagged as credentials since the macOS round (the.config/gh/hosts.ymlandAppData/Roaming/GitHub CLI/hosts.ymllines), because the login they name is the account an agent pushed to: an insider's personal account beside the work one. - A read-only question about the repository's pull requests ran
ghthrough theBashtool; the only trace beyond the transcript was one persistedghoutput intool-results/(Persisted output). Nopr-linkline was written, since no pull request was made, and no repository-bound remote conversation is stored locally.
Observed on Linux 2.1.310, with gh 2.46.0 installed and signed in as a throwaway account before
Claude Code was asked:
gh auth login(the browser flow) wrote onlygh's own store:~/.config/gh/config.yml(902 B, mode 0600) andhosts.yml(94 B, mode 0600), which names the account but holds no token;gh auth statusreported the token in the keyring, and the GNOME keyring file (the Ubuntu desktop's encrypted password store)~/.local/share/keyrings/login.keyring(719 B, mode 0600) was rewritten in the same second. It also created~/.gitconfigwithgh's git credential helper lines; the scopes weregist,read:org,repoandworkflow. Nothing under a Claude Code root changed. A headless host, with no keyring, would have the token inhosts.yml(not observed)./install-github-appfound the app already installed on the account and said so. Unlike 2.1.309,.claude.jsonthen gainedgithubWebConnectionStatusCache{accountUuid, orgUuid, status}withstatusconnected, naming the connected claude.ai account and organisation, besidetipsHistoryByCommand.install-github-app. There was still nogh-pr-status-cache.jsonand nopr-linkline, the command was twouserlines, and the repository list is still onlygithubRepoPaths.- The read-only pull-request question ran three
Bashcalls ofgh, allowed by the classifier inautomode, one output persisted totool-results/(49.6 KB); the first shell call also snapshotted the working tree under the temp root'sbash-edit-diff/(Scratch directories).
Observed on macOS 2.1.310, with Homebrew's gh installed:
/install-github-appwithghnot signed in showedGitHub CLI not authenticated, thenOpening browser to install the Claude GitHub Appfor the repository read from the git remote; cancelled there, it left only the command's twouserlines (the outputInstallation cancelled by user).githubRepoPathswas already filled from the remote with no GitHub sign-in at all.gh auth loginover ssh, a browser device-code flow, printed! Authentication credentials saved in plain textand wrote the token into~/.config/gh/hosts.yml(218 B, mode 0600), which names the account, withconfig.yml(1,965 B, mode 0600) in a~/.config/ghof mode 0751, and no keychain item. The same login from Terminal.app in the desktop session stored the token in the login keychain as a generic password, servicegh:github.com, account the GitHub login, and rewrotehosts.ymlto 94 B, the host entry without the token.gh auth logouttruncatedhosts.ymlto 3 B, an empty document, and kept the file. Both forms are collected under thesharedcatalog agent and flagged as credentials (the.config/gh/hosts.ymlline and its Windows twinAppData/Roaming/GitHub CLI/hosts.yml); a 94-byte file means the token sits in the keychain, a 218-byte one holds it./install-github-appwithghsigned in askedSelect GitHub repository(Use current repository: <owner>/<repo>orEnter a different repository), opened the app's install page withImportant: Make sure to grant access to this specific repository, and, once the app was installed, offered1. Set up GitHub Actions workflowsand2. Skip for now (you can run /install-github-app again later); skipping wrote no workflow and pushed nothing..claude.jsongainedgithubWebConnectionStatusCache{accountUuid, orgUuid, status}at the start of that session, before the command ran: the start-up check writes it once the account's GitHub connection exists. No other file under~/.claudecame from the install.- A read-only pull-request question ran two
ghcalls, allowed by the classifier, with one output persisted totool-results/(51,455 B); there was nogh-pr-status-cache.jsonand nopr-linkline, the branch having no pull request.
Remote Control¶
Remote Control lets another device (claude.ai or the mobile app) drive a local Claude Code process through a bridge, the relay through Anthropic's servers that carries the other device's prompts to the local process and its output back. The evidence is the 2.1.295 Linux binary of Source and evidence, the Ubuntu lab install of 2026-10-11, the Windows 2.1.309 round and the Linux 2.1.310 round (Observed).
- Live at collection time.
.claude/sessions/<pid>.jsonis the per-process registry (collector issue 76). Observed keys on Linux 2.1.295:pid,sessionId,cwd,startedAt,procStart,version,peerProtocol,peerFeatures,kind,entrypoint,hostSessionId,pidDomain,messagingSocketPath,name,nameSource,nameSince,status,updatedAt,statusUpdatedAt, with a sibling<pid>.<sha256>.key; evidenceclaude 2.1.295 linux-arm64 @209019746pid:process.pid,sessionId:V(),cwd:we(),startedAt:Date.now(),procStart:await Fl(process.pid), then@209020296peerProtocol:Akr,peerFeatures:HU(),kind:s,entrypoint:a.CLAUDE_CODE_ENTRYPOINT,hostSessionId:Okr(),pidDomain:await BT().kindwasinteractive,statusidle, andhostSessionIdthe Claude Desktoplocal_<uuid>;messagingSocketPathis the local peer socket, not the bridge. On macOS (2026-10-11, CLI 2.1.295 launched from the Code tab) the 612-byte file had the same key set, withentrypointclaude-desktop,kindinteractive,statusidle,nameSourceuser,pidDomaindarwinandhostSessionIda 42-characterlocal_<uuid>; the sibling.keyis in Credentials. On the WSL2 install (2.1.286 to 2.1.289) some files lackednameandstatus. While Remote Control is attached the CLI patchesbridgeSessionIdinto the same file (@209023561async function mHn(e,n){await bn({bridgeSessionId:e},n)}, the helper that also patchesmessagingSocketPath), and the session picker pairs local sessions with the bridge by it (@208451566if(s.bridgeSessionId!==void 0)). So a registry file withbridgeSessionIdset at collection time means that process was under Remote Control when the collector ran. A record can outlive a crashed process until the housekeeping sweep (.last-cleanup), or, as the vendor's table under What survives a delete puts it, until the next launch clears a crash's leftovers (a 2.1.310--debuglog shows[sessionRegistry] sweep permitted (domain linux:<hex>:pid:[...])at the start), so pair the claim with the EDR's process list. On Linux 2.1.310 a session's registry file was 560 or 561 B with 18 keys of the set above (kindinteractive,entrypointcli,namederived asproj-<2 hex>, threepeerFeatures), rewritten at status changes and removed at the exit. - Transcript lines.
turnOrigintakes one of@215584535var zLt=["human","sdk","scheduled","task_notification","auto_continuation","peer","host_synthetic","system","unknown"]; no value names Remote Control, and aremote-restart-auto-resumewake-up maps toauto_continuation(@215585139case"remote-restart-auto-resume":case"ci-monitor":return"auto_continuation"). The CLI derives{kind:"bridge"}for a human prompt sent over the bridge (@214818778function Ioe(e,n){switch(e.kind){case"human":return n===!0?{kind:"bridge"}:{kind:"composer"}), but that value was seen only feeding the hook path; the observed transcript lines carryorigin:{kind:"human"}. On Windows 2.1.309 the prompt sent from the phone was auserline withorigin.kindhuman,turnOriginhumanandpromptSourcequeued, withqueueSkipSlashCommandsand aqueue-operationpair, betweenbridge-sessionlines: the bridge prompt is told apart byqueuedand the surroundingbridge-sessionlines, not by its origin. - Windows 2.1.309.
/remote-controlin an open session, paired with the Claude iPhone app, wrote threebridge-sessionlines (the last, at the disconnect, withbridgeSessionIdnull), asystembridge_statusline withurl,local_commandlines for the start and stop, andbridgeSessionIdin the process'ssessions/<pid>.json(null after the disconnect). Starting it addedagentPushNotifEnabledtosettings.json, the phone push-notification opt-in, and.claude.jsonlater heldhasRemoteEnvironment,hasUsedRemoteControl,remoteControlReadyPushCount,remoteControlReadyPushKey,remoteControlSurfacesSeen(one string),remoteDialogSeen,replBridgePlaceholders,seenNotifications.rc-active-badgeand two push-notification counters. The tool call the phone asked for ran with no approval prompt, the session being inautomode. - Linux 2.1.310. The same records, paired with the Claude phone app: three
bridge-sessionlines, none with a nullbridgeSessionId, abridge_statusline withurl,local_commandlines for the start and stop, aqueue-operationpair, and the phone's prompt as the round's onlyuserline withpromptSourcequeued(withorigin.kindandturnOriginhumanandqueueSkipSlashCommands); the turn'sassistantlines carriedwireIngestContext. The registry file grew to 584 B withbridgeSessionId, which was set to null at the disconnect and kept, not removed.settings.jsongainedagentPushNotifEnabled, and.claude.jsonhasUsedRemoteControl,remoteControlSurfacesSeen(mobile),remoteDialogSeen,replBridgePlaceholders{}andseenNotifications.rc-active-badge. After the disconnect no local file said whether the session was still visible on claude.ai: thebridge-sessionlines, the nulled registry key andhasUsedRemoteControlare the only marks. - macOS 2.1.310, server mode.
claude remote-control, run from the project (2026-10-26), described itself (Take this session with you and pick up right where you left off on any device. ... The session keeps running on this machine.), askedEnable Remote Control? (y/n), then announcedRemote Control is launching in spawn mode, which lets you start new sessions in this project from claude.ai/code or the Claude mobile appand asked for the spawn mode,[1] same-dir(the default) or[2] worktree(each session in its own git worktree;--spawn=same-dir|worktree). Spawn mode is this server form, in which claude.ai starts new local sessions in the folder, rather than the in-session/remote-controlabove, which shares a running console. It printedSpawn mode: same-dir (saved for this folder; press w to change),Connected · logsleuth · main,Capacity: 1/32 · New sessions will be created in the current directory, a device label<hostname>-local-<word>and an environment id printed asnv_<22 chars>. The server process wrote no registry file of its own; its child, a secondclaudeprocess fromversions/2.1.310, gotsessions/<pid>.json(578 B, the 18 keys plusbridgeSessionId, 32 hex, andentrypointsdk-cli) with its.keyand socket. The start wrotebridge-spawn/<cse id>/withappend-system-prompt.txt(24,994 B of text, the system prompt appended to spawned sessions) andmcp.json(491 B,mcpServers.claude-code-remote{alwaysLoad, headers{X-MCP-Server-ID, X-Session-UUID, anthropic-version}, type, url}withtypehttp, a remote MCP endpoint keyed by session ids);projects/<key>/bridge-pointer.json(168 B, the keys of Transcript stores,sessionIdof 32 hex,sourcestandalone); an empty transcript for the child, named by a uuid whose third group starts with5, a name-based id;remoteControlMachineIdandprojects.<path>.remoteControlSpawnMode(same-dir) in.claude.json; andmcp-logs-claude-code-remote/(CLI cache directory). - macOS 2.1.310, a prompt from claude.ai. A new session started on claude.ai for the machine,
with a prompt asking for
ls -la, ran with no approval asked on either side. The child's file (342,863 B, 28 lines) carriedentrypointsdk-clion every line; the prompt'suserline hadpromptSourcesdk,origin.kindandturnOriginhumanandpermissionModedefault, and came through aqueue-operationpair; theBashcall was allowed byconfigwithreasonTypesubcommandResults, a read-only command; the model was the saved default. Nobridge-sessionline was written: the bridge id is in the registry file only. The turn createdprojects/<key>/<session id>/ccr-tip.json(89 B,eventId, a uuid, andupdatedAt), the last claude.ai event the local side processed; "ccr" stands for Claude Code Remote, the name the MCP log directorymcp-logs-claude-code-remoteuses too. - macOS 2.1.310, the stop. Ctrl+C printed
Shutting down 1 active session(s)andEnvironment preserved. Restart `claude remote-control` to reconnect existing sessions.. It removedbridge-spawn/<cse id>/with both files, the child's registry file,.key, lock, socket and shell snapshot; it keptbridge-pointer.json, now naming a dead process, the child's transcript (grown by acost-stateand alast-promptline) with itsccr-tip.json,session-env/, the temp-root directories, andremoteControlMachineIdandremoteControlSpawnMode. NohasUsedRemoteControlorremoteControlSurfacesSeenkey was written. On claude.ai the session stayed listed, marked disconnected, after the stop, as the vendor's Resume sessions after stopping the server describes: the remote copy outlives the local server,bridge-pointer.jsonandccr-tip.jsonbeing the local half of the join. - Historical.
~/.claude.jsonkeyshasUsedRemoteControl,remoteControlUpsellSeenCount,remoteControlSurfacesSeenandremoteDialogSeen(@224457799return!e.hasUsedRemoteControl&&!ore()&&(e.remoteControlUpsellSeenCount??0)<c,@224460306remoteControlSurfacesSeen:[...t,n],@224188242in the global-config key list"remoteDialogSeen"; observed on a macOS install that has used the feature) say the account used Remote Control on this host at some time. - Not Remote Control.
.session_ingress_token,remote/.oauth_tokenandCLAUDE_CODE_REMOTE_SESSION_IDbelong to cloud-hosted sessions, where the CLI itself runs remotely, as@206668216setHasRemoteSessionOrContainerId(Boolean(a.CLAUDE_CODE_REMOTE_SESSION_ID||a.CLAUDE_CODE_CONTAINER_ID))shows; a different feature, whose files are in Credentials.
Sync, relay and origin rows says which rows the parser makes of these fields (issue 80).
Cloud sessions¶
A cloud session is a Claude Code session that runs on Anthropic's servers against a GitHub
repository; to teleport one is to bring it into a local session that continues it. Neither the
Windows nor the Linux round was offered one. On macOS 2.1.310 (2026-10-26) the help listed
--cloud [description|session_id|url], --environment <environment_id>, --teleport [session],
--remote-control [name] and --bg, and the subcommands agents, attach, logs, respawn,
purge, auto-mode, auth, import, gateway and ultrareview, but no config.
claude --cloud "<task>"in the project printedCreated cloud session: <title>,View: <url>andResume with: claude --teleport session_01<22 chars>within three seconds and returned to the shell, the account's GitHub connection supplying the repository. Locally it created the emptyseed-admin/(mode 0700), addedhasRemoteEnvironmentto.claude.json, made a temp-root directory and rewrote one tree object in the project's.git/objects/(Project-local files). It wrote no.session_ingress_token,remote/,server-sessions.jsonorremote-settings.json, and no transcript: the session's id is only printed, and its record is on the servers.claude --teleport session_01<id>in the project found the uncommitted change and askedWorking directory has changes · Teleport will switch git branches. ... Would you like to stash these changes and continue with teleport?;1. Stash changes and continuerangit stash(Teleport auto-stash) and switched no branch, the cloud session having made no commit. The cloud session's turn was replayed on screen,Session resumedshown, and the session continued inautomode, with its registry file (entrypointcli),.key, lock, socket, a temp-root directory and connector logs, but no transcript yet.- The first local prompt created the transcript (427,839 B, 56 lines), holding the cloud turn and
the local one: the replayed cloud
userandassistantlines carryremoteSourced(true, 7 lines), the cloud promptpromptSourcesdk,origin.kindhumanandpermissionModedefault, the local prompttypedandauto; every line saysentrypointcli. The cloud session id appears 7 times, inside URL and text values only, and the wordteleportnot at all. The replayed lines include astop_hook_summarynaming a hook of the cloud environment, aninformationalline, a decision withreasonTyperuleandruleSourcecliArg, and anenvironmentattachment naming the cloud machine's scratchpad (attachmentrecords).
Format versions¶
- Versions 2.1.286 to 2.1.289 observed with the same shapes. The parser docstring says "Claude Code 2.x"; nothing older was available.
summary({type, summary, leafUuid}) andprogressrecords are still read by 2.1.289 (cc:484009, cc:484055) andprogresscan still be written by forked agents (cc:482596); neither was observed. The summary type is the legacy form of session titles, nowai-titleandcustom-title.thinkingblocks are mostly empty strings with asignature(3,165 of 3,525): the thinking text was not returned, not redacted;redacted_thinkingis a separate block type.- Compaction (
compact_boundaryplus auserrecord withisCompactSummary) was not observed before 2.1.309;relocatedwas never observed. - 2.1.309 (Windows 11 arm64, 2026-10-23), against 2.1.289: the request capture lines
api-request-shape,api-requestandapi-request-blobare written by default and hold more of the file than the conversation (Record schema);permissionDecisiongainsgrantScopeandreasonCode, andsourcethe valuesuser_temporary,user_permanentanduser_reject(Permission decisions); worker and subagent lines carrysessionKindandslug(Transcript envelope);continued-inandcost-statelines mark the session daemon's handover, which writes the worker's own transcript file (Session daemon records);assistantlines addrequestRef,thinkingDisplayand theattributionMcp*keys anduserlinessandboxed,mcpMeta,queueSkipSlashCommandsand the scheduled-task keys (assistantrecords,userrecords); the attachment typesauto_mode_exit,session_cron_carry,task_reminder,plan_file_referenceandfileare new (attachmentrecords);compact_boundaryandisCompactSummaryare observed;history.jsonlpastedContentscarries{contentHash, id, type}(history.jsonl);custom-title.jsonis written beside the session file; the session daemon,jobs/,skills/synced/,plugins/synced/andstate/directories are written (Per-user storage); the settingcleanupPeriodDaysmust be at least 1 (What survives a delete); and a prompt typed during a turn is no longer aqueued_commandattachment (User-record kinds). The record types of 2.1.289 that 2.1.309 still writes kept their keys, with the additions named here. - 2.1.310 (Ubuntu 26.04.1 arm64, 2026-10-25 and 2026-10-26), against 2.1.309: every line type and
key set of the transcript is unchanged;
/modelsaves the pick as the default for new sessions without asking (Joins and timestamps); the allow-always option offers a wildcard prefix rule ("Always allow python3 *") rather than the exact command (Permission configuration);.claude.jsonrecords a GitHub connection ingithubWebConnectionStatusCache(Source-host integration);permissionDecision.reasonCodetakes the valuebashMiss:no-rule-match, andreasonTypesubcommandResultsappears on aconfigaccept (Permission decisions);promptSourcesuggestion_acceptedwas seen on the/scheduleflow's prompt line (userrecords);.meta.jsonaddsisBuiltIn(Transcript stores); a project entry in.claude.jsonaddslastFpsAverageandlastFpsLow1Pct(.claude.json); and a shell call snapshots the working tree under the temp root'sbash-edit-diff/(Scratch directories). Some differences from the Windows round are the platform's, not the version's: the.keyfile'sprocStartforprocStartFt, the symlink launcher, thefile-historybackup's modification time. - 2.1.310 on macOS 26.6.2 arm64 (2026-10-26), against the same build on Linux: a refusal in a
permission dialog, with or without its amend option, and a declined plan were recorded with
toolDenialKindand nopermissionDecisionoruserFeedback(Permission decisions);auto_modecame at a first turn that ran only aRead, andauto_mode_exitwas written (attachmentrecords); the second start offeredTry the new fullscreen renderer?(Flicker-free output,Mouse support,Selected text auto-copies to your clipboard;1. Yes, try it,2. Not now);/modelaskedSwitch model?before switching, still saving the pick as the default (Joins and timestamps), and was ahistory.jsonlline; a subagent ran on the account default after a switch; theconfigsubcommand is gone, socleanupPeriodDaysis set insettings.jsononly;--cloudand--teleportworked for this account (Cloud sessions);claude remote-controlruns a server in spawn mode, which writesbridge-spawn/,bridge-pointer.jsonandccr-tip.jsonand nobridge-sessionline (Remote Control);scheduled_tasks.lockis removed at/exit; and the official plugin marketplace arrives as a download, not a git clone (Files that hold evidence). The help overlay (?or/help) listed! for shell mode,/ for commands,@ for file paths,/btw for side question,double tap esc to clear input,shift + tab to auto-accept edits,ctrl + o for verbose output,ctrl + t to toggle tasks,backslash + return for newline,ctrl + shift + _ to undo,ctrl + z to suspend,ctrl + v to paste images,opt + p to switch model,opt + o to toggle fast mode,ctrl + s to stash prompt,ctrl + g to edit in $EDITORand/keybindings to customize. - No compressed or archived transcript form exists; deletion by
cleanupPeriodDaysis the only rotation.
Secrets¶
~/.claude.json, the global config:oauthAccount(the sign-in session) and the MCP server definitions'envblocks and headers, globally and underprojects.<path>.mcpServers. The parser reads only its permission keys (Permission configuration). On Windows 2.1.309oauthAccountheldaccountUuid,emailAddress,fullName,displayName,organizationUuid,organizationName,organizationRole,organizationType,billingType,accountCreatedAtandsubscriptionCreatedAtamong others;machineIDanduserID(64 hex each) identify the install,cachedArtifactRosterandclientDataCacheSlotsname the account and organisation again, andgithubRepoPathsnames the GitHub repositories (.claude.json). Everybackups/.claude.json.backup.<epoch ms>holds the same. On Linux 2.1.310oauthAccounthad 20 keys,hasExtraUsageEnabled,seatTierandworkspaceRoleamong them, andgithubWebConnectionStatusCachenames the account and organisation of the GitHub connection byaccountUuidandorgUuid. On macOS 2.1.310 the 20oauthAccountkeys wereaccountCreatedAt, accountUuid, billingType, ccOnboardingFlags, claudeCodeTrialDurationDays, claudeCodeTrialEndsAt, displayName, emailAddress, fullName, hasExtraUsageEnabled, organizationName, organizationRateLimitTier, organizationRole, organizationType, organizationUuid, profileFetchedAt, seatTier, subscriptionCreatedAt, userRateLimitTier, workspaceRole;remoteControlMachineIdis a uuid naming the machine to Remote Control; and thebackups/copies keepoauthAccountafter a/logout..credentials.jsonon Windows 2.1.309 and Linux 2.1.310:claudeAiOauth{accessToken, refreshToken, expiresAt, refreshTokenExpiresAt, scopes, subscriptionType, subscriptionTypeSource, rateLimitTier},scopessix strings.sessions/<pid>.<sha256>.keyon Windows 2.1.309:{peerToken, pidDomain, procStartFt},peerToken32 hex; on Linux 2.1.310{peerToken, pidDomain, procStart},procStarta decimal string, the process's start tick; on macOS 2.1.310 the Linux set withpidDomaindarwin.ide/<port>.lockholdsauthToken, a uuid that authenticates to the VS Code extension's listener;bridge-spawn/<cse id>/mcp.jsonholds theX-MCP-Server-IDandX-Session-UUIDheaders that identify a Remote Control session to the remote endpoint;bridge-pointer.jsonnames the Remote ControlenvironmentIdandccr-tip.jsonthe last claude.aieventId(Remote Control).daemon/control.key(32 B of hex on Linux) anddaemon/pipe.keyare raw key bytes, anddaemon/roster.jsoncarriesptyAuthandrvAuthper worker (Session daemon records).- Account and organisation ids in names:
skills/synced/<org uuid>_<account uuid>/,plugins/synced/<org uuid>_<account uuid>/andcache/model-catalog/<org uuid>-.... - The settings files:
env(such asenv.ANTHROPIC_API_KEY),apiKeyHelper, hooks and MCP server settings and headers; the parser never reads them. - Identity inside the transcript: the
session_contextattachment carriescontext{userEmail, gitStatus},credential_organorganizationUuid, andbridge-sessionrecordsownerAccountUuidandownerOrganizationUuid(Record schema); thebridge_statusline'surlis the Remote Control pairing URL, and everyapi-request-blobrepeats a request message with the prompt text. - The credential files of the config home are in Credentials.
Parser plan¶
doubleagent/parsers/claude_code.py, class ClaudeCodeParser. wants():
^\.claude(?:-[^/]+)?/projects/[^/]+/.*\.jsonl$
^\.claude(?:-[^/]+)?/history\.jsonl$
^\.claude(?:-[^/]+)?/sessions/\d+\.json$
^\.claude(?:-[^/]+)?/settings(?:\.local)?\.json$
^(?:\.claude-[^/]+/)?\.claude\.json$
^\.claude(?:-[^/]+)?/projects/[^/]+/([^/]+)/ccr-tip\.json$
^\.claude(?:-[^/]+)?/projects/[^/]+/bridge-pointer\.json$
The .claude-<name> form is a config home moved with CLAUDE_CONFIG_DIR; what the catalog
collects from one is under Relocated config homes. A
project's .claude/settings.json and .claude/settings.local.json reach the parser as project
artifacts (the collector's project catalog has project|.claude), with the project directory as
their home. The parser also reads a project's projects/<key>/bridge-pointer.json and a session's
projects/<key>/<session id>/ccr-tip.json, the Remote Control marks of
Sync, relay and origin rows.
Lines are read with iter_jsonl; each bad line becomes the row described under Bad
lines. Each session file is read twice: a first pass collects the prompts that
queued_command attachments and user records deliver and the lines that deliver them, whether the
file holds a <fork-boilerplate> block, and the first cwd and gitBranch.
Coverage. A label is the start of a system row's text, <label>: and then the full text:
| Record | turn_type |
|---|---|
| subagent file, first transcript record | an extra system row first, subagent <agentId> of <sessionId> (agentId from the record, else from the file name) |
user, first one in a subagent file that is not a fork |
system, label subagent task |
user, isCompactSummary |
system, label compaction summary |
user, interruptedMessageId |
system, label interrupted |
user text starting <task-notification> |
system, label task notification |
user text starting <command-name> or <command-message> |
system, label slash command |
user text starting <local-command-stdout> |
system, label command output |
user text starting <local-command-caveat> or <system-reminder> |
system, label context |
user text starting <fork-boilerplate> |
system, label subagent task |
user, origin.kind task-notification, peer, coordinator, auto-continuation |
system, label task notification, peer message, coordinator message, auto-continuation |
user, other isMeta |
system, label context |
user, any other string, text or image block |
user (one row per block); the rules above are tried in this order, the record-level ones for every block |
user, tool_result block |
tool_result; tool_reference blocks as their tool_name; a <persisted-output> stub replaced by its tool-results/ file, named by toolUseResult.persistedOutputPath when that key is set and the line has one tool_result block, else by the stub's text (below); text prefixed [error] when is_error |
user, permissionDecision (2.1.295) |
one system row after the line's tool_result rows, permission decision: <decision> source=<source> reason=<reasonType> (- for a missing key), then a space and the object's compact JSON when it has other keys (grantScope and reasonCode on 2.1.309, Permission decisions); tool_use_id of the line's first tool_result block, tool_name empty as on tool_result rows |
user, toolDenialKind, userFeedback (factory key list, Record schema) |
appended to that line's permission decision: row as toolDenialKind=<value> userFeedback=<value>, in that order, each only when set, printed as recorded (no value set is recorded; userFeedback is free text, so it comes last); a line with toolDenialKind and no permissionDecision, the form 2.1.310 wrote on macOS for every refusal and a declined plan, gets permission decision: reject (interrupted) toolDenialKind=user-rejected (the value as recorded), the source being unknown (issue 97) |
attachment plan_mode, plan_mode_exit, plan_mode_reentry, auto_mode, auto_mode_exit |
skip: their keys are now recorded (attachment records); auto_mode is the turn's auto-mode configuration, not a decision, and every mode change they mark is also a permissionMode value or a permission-mode record, which the permission mode: rows read |
first record of a transcript file that carries entrypoint |
before its rows, a system row session surface: <entrypoint> (values cli, claude-vscode, claude-desktop for the Code tab, sdk-cli for a session a Remote Control server spawned), and session surface: <old> -> <new> on a change; a subagent file gets its own row |
a session whose lines carry entrypoint sdk-cli and whose prompt has promptSource sdk |
system, session origin: remote (Remote Control, entrypoint sdk-cli) at its first line (Sync, relay and origin rows) |
first line with remoteSourced true |
system, session origin: remote (teleport, remoteSourced), once per session, and beside it session synced: cloud session (teleported: remoteSourced records replayed from the vendor's copy), since the vendor holds the conversation (synced yes); the cloud session_01... id, which sits inside URL and text values, is not extracted |
a session whose lines carry entrypoint claude-vscode |
system, session origin: local (VS Code extension); promptSource sdk on such a prompt is not a remote mark on its own |
user record with promptSource set and not typed, or turnOrigin set and not human |
after the record's rows, a system row prompt trigger: promptSource=<promptSource> turnOrigin=<turnOrigin>, - for a missing key; the pairs seen on Windows 2.1.309 are promptSource=suggestion_accepted turnOrigin=human, promptSource=system turnOrigin=task_notification (a subagent's or a background command's end), promptSource=queued turnOrigin=human (the Remote Control phone), promptSource=system turnOrigin=scheduled (a /loop firing) and promptSource=sdk turnOrigin=human (the VS Code extension); origin.kind still picks the label above |
continued-in |
system, session continued in: <continuedInSessionId>, at the record's timestamp |
bridge-session |
system, remote control: bridge session <bridgeSessionId or none> lastSequenceNum=<lastSequenceNum or ->, written when the (id, sequence) pair changes, none being the detach; the owner uuids are never printed. At the first record naming an id, once per session and bridge id, session synced: remote control bridge <bridgeSessionId> url <url> (Sync, relay and origin rows) |
attachment environment with snapshot.scratchpadDirectory |
system, scratch directory: <path> (session scratchpad, from the environment attachment), once per session and path, project_path the scratchpad path (Scratch directory rows) |
user permissionMode, permission-mode record |
one tracker per session id in the file: the first value is a system row permission mode: <mode>, a later different value permission mode: <old> -> <new>, before the line's other rows; a user line's row takes its timestamp, a permission-mode record (no timestamp) the latest one seen; an unchanged value, such as a re-appended state record, yields nothing; a subagent file has its own tracker |
assistant message.model (not <synthetic>, not an API error) |
before the line's rows, model: <id> at the session's first one and model: <old> -> <new> on each change, with requested=<requestedModel> when that key is set and differs; model column the new model; a subagent file has its own tracker |
assistant, isApiErrorMessage or model <synthetic> |
system, api error: <apiErrorStatus> <error>: <text>, no model |
assistant, text block |
assistant (empty text skipped) |
assistant, tool_use block |
tool_use |
assistant, thinking |
thinking with include_thinking; an empty thinking (signature only) is skipped |
assistant, redacted_thinking |
thinking with include_thinking, text [redacted] |
assistant, other block types |
skip |
system scheduled_task_fire |
system, scheduled task fired: <taskId> cron=<cron>; the next user line, with turnOrigin scheduled, keeps its prompt trigger: row |
system |
system, text <subtype>: <content>; turn_duration as <durationMs> ms, <messageCount> messages; without content, <subtype>: and the compact JSON of the fields other than the envelope (api_error: level, retryInMs, retryAttempt, maxRetries, error) |
attachment queued_command, origin.kind human |
user, text prompt (a text marker above still makes it system) |
attachment queued_command, other origin |
system, the marker or origin label, else queued command |
attachment hook_system_message |
system, hook: <hookName>: <content> |
attachment edited_text_file |
system, edited file: <filename> (the snippet is not shown) |
attachment, other types |
skip: model context (system prompt, tool lists, reminders, environment and model snapshots, skill and agent listings); remote_session_change (the session's PR and commit) is skipped too |
ai-title, custom-title, agent-name |
system, session title: <value>, once per distinct value per session |
relocated |
system, cwd changed: <relocatedCwd>; every later row of the file takes it as project_path |
pr-link |
system, pr-link: <prUrl or prNumber>, once per session and URL (the first record wins) |
queue-operation |
system, queue <operation>: <content>; an enqueue whose content a queued_command attachment or a user record of the file delivers is prompt queued: delivered at line N at the enqueue's own timestamp, N being the first delivering line after it (else the first), so the typing time is kept and the prompt text appears once, on the delivering row |
| every other type (Record schema table) | skip |
history.jsonl line |
user, each paste placeholder in display expanded (Past-use rows and secondary sources) |
sessions/<pid>.json |
one system row at startedAt (epoch ms), claude-code process <pid> <kind> status=<status> entrypoint=<entrypoint> bridge=<bridgeSessionId or none> host_session=<hostSessionId or none> (- for a missing kind, status or entrypoint), session_id sessionId, project_path cwd, source_line 1; a file that is not a JSON object is one system row parser: session registry file is not a JSON object. A bridge= value means the process was under Remote Control when the collector ran (Remote Control), and the file then gives a second system row, session synced: remote control bridge <bridgeSessionId> url - (from <path>, file mtime), at the file's modification time (Sync, relay and origin rows) |
projects/<key>/<session id>/ccr-tip.json |
one system row at the file's modification time, session synced: remote event <eventId> at <updatedAt> (from <path>, file mtime), session_id the directory's session (Sync, relay and origin rows) |
projects/<key>/bridge-pointer.json |
one system row at the file's modification time, remote control: environment <environmentId> session <sessionId> source=<source> (from <path>, file mtime) |
settings.json, settings.local.json (user, .claude-<name>, or a project's) |
one system row per permissions.allow, deny, ask and additionalDirectories entry, then one for permissions.defaultMode and permissions.disableBypassPermissionsMode when set: permission setting: permissions.<key>=<value> (from <path relative to the home or project>, file mtime), a non-string value as compact JSON; timestamp_utc the manifest mtime, else the file's own; session_id empty; project_path the project directory for a project artifact, else empty; source_line 1; beside them, when cleanupPeriodDays is set, one system row retention setting: cleanupPeriodDays=<n> (from <path>, file mtime) at the same time, since the value decides what the retention sweep removes (What survives a delete); when model is a non-empty string, one system row model setting: model=<alias> (from <path>, file mtime) at the same time (Model rows); no other key read; a file that is not a JSON object is one system row parser: settings file is not a JSON object |
.claude.json, .claude-<name>/.claude.json |
per projects.<path>: one row per allowedTools entry, then one each for hasTrustDialogAccepted and hasClaudeMdExternalIncludesApproved when present, permission setting: <key>=<value> (from .claude.json, file mtime), project_path the <path> key, times as above; when githubWebConnectionStatusCache is present (2.1.310), one system row source host: github status=<status> (from .claude.json, file mtime) at the file's modification time, its accountUuid and orgUuid never printed (Sync, relay and origin rows); at the same time, remote control: machine id set when remoteControlMachineId is present, remote control: spawn mode <mode> for <path> per projects.<path>.remoteControlSpawnMode, and cloud: hasRemoteEnvironment=<value> (from .claude.json, file mtime), no uuid printed; nothing else in the file is read (it holds the account's OAuth details); .claude.json.backup* is not wanted |
Columns:
| Column | Source |
|---|---|
| timestamp_utc | record timestamp; records without one (titles, relocated) take the latest timestamp seen in the file; history: timestamp (ms) |
| session_id | latest sessionId (or session_id) seen in the file; history: sessionId |
| project_path | the relocatedCwd of an earlier relocated record, else the latest cwd seen in the file, else the file's first cwd; history: project |
| git_branch | the latest gitBranch seen in the file, else the file's first |
| turn_type | coverage table |
| model | message.model on assistant and thinking rows |
| tool_name | tool_use.name; empty on tool_result rows |
| tool_use_id | tool_use.id / tool_result.tool_use_id |
| text | user/assistant/thinking text; tool_use: for Bash the command of wireToolInputs[<id>] when present (it keeps the cd <dir> && prefix), else per-tool key from input (command, file_path, notebook_path, pattern+path, url, query, description+prompt, skill+args, path), else the first of command, file_path, path, pattern, url, query, description, else compact JSON; tool_result: the block text as above |
| source_line | 1-based line number; the subagent header row takes its first record's line |
Not read: .meta.json sidecars, the sessions/<pid>.<sha256>.key files (secrets), the other
sessions/<pid>.json keys (procStart, version, peerProtocol, peerFeatures,
pidDomain, messagingSocketPath, name, nameSource, nameSince, updatedAt,
statusUpdatedAt), the 2.1.295 assistant keys (advisorModel, apiBlockIndex, effort,
perTurnEffort, serverClassifierRequest, thinkingDurationMs; requestedModel is read only for
the model row and wireToolInputs only for Bash), sourceToolAssistantUUID and
serverClassifierContext, the 2.1.309 request capture lines (api-request-shape, api-request,
api-request-blob), cost-state (the line that ends a file at /clear included), mode,
atis-latch, custom-title.json (the custom-title line is read), bridge-spawn/, seed-admin/,
ide/<port>.lock, cache/model-catalog/, the session daemon's files (daemon.log,
roster.json) and jobs/,
backups/, file-history/, plans/, state/mcp-discover-verdicts.json, skills/synced/,
plugins/synced/, the registry keys formerNames, parkedJobId and jobId, sessionKind (kept
in the row's envelope, not printed), githubRepoPaths, the debug/ logs written by
claude --debug, which are not a store the parser reads, and the claude-cli-nodejs MCP logs
(CLI cache directory), whose lines name the session, the project and a
time, so each debug message could become a system row joined to the session.
Permission rows¶
Per-call decisions are the permission decision: rows of permissionDecision, toolDenialKind
and userFeedback (issue 97), the mode the
permission mode: rows, and the standing grants the permission setting: rows of the settings
files and .claude.json (issue 93); the
coverage table has the exact texts, Record schema and
Permission configuration the records. On 2.1.309 and 2.1.310 the
decision row covers every form of Permission decisions: the classifier's
and the mode's automatic accepts (source=config reason=classifier or reason=mode, and on
2.1.310 reason=subcommandResults), the one-time approval (source=user_temporary, grantScope
once in the JSON suffix), the allow-always rule's use (source=user_permanent), the denial
(source=user_reject with toolDenialKind and userFeedback) and the declined plan (a rejected
ExitPlanMode); the reasonCode values, bashMiss:no-rule-match on 2.1.310 among them, are in
the JSON suffix. The refusal with no decision record that macOS 2.1.310 wrote, for a shell command
and for a declined plan alike, is
permission decision: reject (interrupted) toolDenialKind=user-rejected; the reason the person
typed afterwards is the next user row. The allow-always grant itself is a
permission setting: permissions.allow=PowerShell(...) row from the project's
settings.local.json on Windows, and a Bash(...) one on Linux. The plan_mode,
plan_mode_exit, plan_mode_reentry, auto_mode and auto_mode_exit attachments are not read;
the mode changes they mark are permission mode: rows.
Model rows¶
The model: rows come from message.model and requestedModel (the assistant row of the
coverage table). A /model switch keeps its own slash command and command output rows
(Joins and timestamps). The model attachment and cost-state records are
not read. Confirmed on Windows 2.1.309 and Linux and macOS 2.1.310: the lines carry the resolved
id only, so the change row is the evidence of a switch. The alias the person picked is in
settings.json model, read as the model setting: row of the coverage table, a row that also
shows a change no /model made: the VS Code extension's start rewrote the key on macOS 2.1.310
(Joins and timestamps). A subagent's
toolUseResult.resolvedModel that differs from the parent's model yields nothing new,
since the subagent file's assistant lines carry that model.
Past-use rows and secondary sources¶
.claude/history.jsonl is read on every run as a secondary source of the transcripts, one user
row per line (history.jsonl); it usually outlives its transcript (What survives
a delete), the retention sweep keeping it when it deletes the transcript,
so a session found only in history.jsonl is the expected trace of a swept session. The
sessions/<pid>.json registry and the permission settings yield rows of their own. A prompt sent
through claude agents or from the VS Code extension's panel is in the transcript only
(history.jsonl). Not read: file-history/ and the file-history-* records,
backups/ (dated copies of .claude.json), jobs/ and the daemon's files, the .meta.json
sidecars and the claude-cli-nodejs MCP logs, which each name the session, the
project and a time and so could become rows of their own. A history line whose display is a
slash command (/model, /rename ..., /clear, /loop ...) matches the transcript's user line
that wraps the same command in <command-name> and <command-args> tags.
Before the match, each [Pasted text #N] or [Pasted text #N +M lines] placeholder in a history
line's display is replaced by the paste's text: pastedContents.<N>.content when the paste is
kept inline (history.jsonl), else the file paste-cache/<contentHash>.txt
beside history.jsonl (Per-user storage). The text is found only when
pastedContents is an object with an entry for that number whose type is text or absent, and
the entry's content is a string or its contentHash is a plain alphanumeric name that names a
regular file in paste-cache/, neither the directory nor the file a symlink; any other placeholder
is kept as written. A cache file that is there but cannot be read keeps the
placeholder too and adds a system row
parser: paste-cache/<contentHash>.txt unreadable (<error>) after the file's rows, source_line
the history line. A prompt then matches with the transcript's <pasted_content> wrapper removed
(user records) and each run of ASCII whitespace collapsed to one space, so an
expanded history line matches the user line whose text is the same once unwrapped, and only a
history line with no transcript counterpart is a recovered row.
Sync, relay and origin rows¶
The agent records sync through Remote Control. A bridge-session line becomes a
remote control: bridge session <bridgeSessionId or none> lastSequenceNum=<lastSequenceNum or ->
row whenever its id and sequence pair changes, none marking the detach. At the first
bridge-session line that names an id, once per session and bridge id, the parser also yields
session synced: remote control bridge <bridgeSessionId> url <url>, the url being that of the
bridge_status system line that belongs to the bridge: among the status lines between the
previous and the next bridge-session line of another id (a detach counts), the first at or
after it, else the nearest before it, else -, so a second bridge in one file never takes the
first bridge's URL, and it sets
records_sync, so sessions.jsonl synced reads yes or no for this agent's sessions. The
row rests on the bridge_status URL (https://claude.ai/code/session_<id>) naming the session on
claude.ai while the bridge is up; for the server form, claude.ai kept the session listed after the
local stop (Remote Control). A sessions/<pid>.json registry file that names a
bridgeSessionId gives the same
marker, session synced: remote control bridge <bridgeSessionId> url - (from <path>, file mtime),
at the file's modification time, because the id says the process was under Remote Control when
the collector ran (Remote Control); it is the marker that is left when the
transcript is gone, and a session with both keeps both rows, which sessions.jsonl counts once
as synced yes. The daemon handover is local and is a session continued in:
row, not a sync row.
The server form of Remote Control (macOS 2.1.310) writes no bridge-session line, so its marks are
files and keys. A session it spawned, whose lines carry entrypoint sdk-cli and whose prompt
promptSource sdk, gets session origin: remote (Remote Control, entrypoint sdk-cli) at its
first line; its ccr-tip.json gives
session synced: remote event <eventId> at <updatedAt> (from <path>, file mtime) at the file's
modification time, with the session's own id; the project's bridge-pointer.json gives
remote control: environment <environmentId> session <sessionId> source=<source> (from <path>,
file mtime) with an empty session id (pid and procStart are not printed); and .claude.json
gives remote control: machine id set (from .claude.json, file mtime) and
remote control: spawn mode <mode> for <path> (from .claude.json, file mtime), with no uuid
printed. A teleported cloud session gets session origin: remote (teleport, remoteSourced) once,
with session synced: cloud session (teleported: remoteSourced records replayed from the vendor's
copy) beside it (the vendor holds the conversation, so synced reads yes),
at its first line with remoteSourced; its replayed environment attachment, naming the cloud
machine's scratchpad, yields session origin: remote (teleport, cloud scratchpad <path>) in place
of the scratch directory: row. The replayed records come first in the file and the local
process's own after them, so the attachment is the replayed one when it sits on or before the
file's last remoteSourced line, whatever its project slug: the cloud checkout and the local one
may share a path (/home/user/repo), and a local slug may differ by case
(issue 112) or after a relocated
record. A cloud session created with --cloud and never teleported leaves no local session, so
its only row is cloud: hasRemoteEnvironment=<value> (from .claude.json, file mtime); the
files the bundle names for one (.session_ingress_token, remote/, server-sessions.json,
remote-settings.json) were not written. A VS Code extension session gets
session origin: local (VS Code extension). The parser sets records_sync and records_origin,
so sessions.jsonl synced reads yes or no and origin remote or local for every
session of this agent. The agent records
no relay, so the parser does not set records_relay. The remote_session_change attachment,
present in plain local sessions with nothing to link, is skipped. The source host is
a row since 2.1.310: .claude.json githubWebConnectionStatusCache gives one system row
source host: github status=<status> (from .claude.json, file mtime) at the file's modification
time, the account and organisation uuids not printed
(Source-host integration); 2.1.309 wrote no such key, so its homes
have no such row. The GitHub account's login and token stay in gh's store, which is not
collected, and githubRepoPaths is not read.
Scratch directory rows¶
The environment attachment's snapshot.scratchpadDirectory becomes a system row
scratch directory: <path> (session scratchpad, from the environment attachment), once per session
and path, whose project_path is the scratchpad path, so inventory.json lists it under
scratch_directories. On Windows the path
is under the collected temp root AppData/Local/Temp/claude/<slug>/<session id>/scratchpad
(Scratch directories); on Linux and macOS the temp root is outside the home
and not collected (issue 163), so the row
names a directory the collection lacks. On Linux the temp root also holds one directory per
process, so a resume, a --continue, claude agents or claude mcp add leaves directories that a
session's row may not name, and the whole tree is gone after a reboot or when the retention sweep
deletes the session's transcript. On macOS the temp root, /private/tmp/claude-<uid>, is not
collected either and is gone after a reboot (Scratch directories); a
teleported session's row names the cloud machine's scratchpad, a path that never existed on the
host (Cloud sessions). The files under the temp root's tasks/ and
bash-edit-diff/ are not read.
Deletion rows¶
The agent records none: no deletion marker is stated or was seen on Windows 2.1.309 or on Linux
and macOS 2.1.310 (What survives a delete). A /clear deletes nothing:
it is the command's rows (a local_command row, local_command: and the command, where the build
writes one, and the slash command row of its user line) at the start of the new session's
file, and the cost-state line that ends the old file is not read. A /logout is no row either:
it removes .credentials.json and the account keys of .claude.json. A removed
project is not a row: the analyzer's inventory lists it with the referenced status, a project the
collected files still name with no files of its own. The retention sweep deletes silently, so the
setting that drives it is the row: cleanupPeriodDays in a settings file gives
retention setting: cleanupPeriodDays=<n> (from <file>, file mtime) (coverage table), and a
session whose transcript the sweep removed remains as its history.jsonl rows
(Past-use rows and secondary sources).
Bad-line rows¶
Each bad line becomes its own system row at the end of the file's rows, source_line its line
number and text parser: unparseable line (<decoder message>): <the line in full>
(issue 136); the rows before and after it
are kept. The session sample under Fixtures ends with a cut line for this case. An
unreadable paste-cache/ file gives a row of its own
(Past-use rows and secondary sources).
Fixtures¶
Sample records, .claude/projects/-srv-proj/11111111-2222-4333-8444-555555555555.jsonl (state
record, typed prompt, empty-thinking block, Bash call with a wire input, its result, a prompt queued
mid-turn and its attachment, reply, turn duration, title, last prompt, PR link, backup snapshot,
then a cut line):
{"type":"mode","mode":"normal","sessionId":"11111111-2222-4333-8444-555555555555"}
{"parentUuid":null,"isSidechain":false,"promptId":"0c0c0c0c-0000-4000-8000-000000000001","type":"user","message":{"role":"user","content":"why does the test fail?"},"uuid":"00000000-0000-4000-8000-000000000001","timestamp":"2026-10-01T10:00:00.000Z","permissionMode":"auto","origin":{"kind":"human"},"promptSource":"typed","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-8000-000000000001","isSidechain":false,"message":{"model":"claude-fable-5-1","id":"msg_01","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"c2lnbmF0dXJl"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":900,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":40}},"requestId":"req_01","apiBlockIndex":0,"type":"assistant","uuid":"00000000-0000-4000-8000-000000000002","timestamp":"2026-10-01T10:00:02.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-8000-000000000002","isSidechain":false,"message":{"model":"claude-fable-5-1","id":"msg_01","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01","name":"Bash","input":{"command":"npm test","description":"Run the tests"},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":900,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":60}},"requestId":"req_01","apiBlockIndex":1,"wireToolInputs":{"toolu_01":{"command":"cd /srv/proj && npm test","description":"Run the tests"}},"wireIngestContext":{"toolu_01":{"cwd":"/srv/proj"}},"type":"assistant","uuid":"00000000-0000-4000-8000-000000000003","timestamp":"2026-10-01T10:00:03.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-8000-000000000003","isSidechain":false,"promptId":"0c0c0c0c-0000-4000-8000-000000000001","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":"1 failing","is_error":false}]},"uuid":"00000000-0000-4000-8000-000000000004","timestamp":"2026-10-01T10:00:05.000Z","toolUseResult":{"stdout":"1 failing","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false},"sourceToolAssistantUUID":"00000000-0000-4000-8000-000000000003","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-01T10:00:05.500Z","sessionId":"11111111-2222-4333-8444-555555555555","content":"also check the linter"}
{"parentUuid":"00000000-0000-4000-8000-000000000004","isSidechain":false,"attachment":{"type":"queued_command","prompt":"also check the linter","commandMode":"prompt","origin":{"kind":"human"},"source_uuid":"00000000-0000-4000-8000-0000000000a1","delivery_id":"0d0d0d0d-0000-4000-8000-000000000001","timestamp":"2026-10-01T10:00:05.500Z"},"type":"attachment","uuid":"00000000-0000-4000-8000-000000000005","timestamp":"2026-10-01T10:00:06.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-8000-000000000005","isSidechain":false,"message":{"model":"claude-fable-5-1","id":"msg_02","type":"message","role":"assistant","content":[{"type":"text","text":"The fixture date is stale; the linter is clean."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":950,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":20}},"requestId":"req_02","apiBlockIndex":0,"type":"assistant","uuid":"00000000-0000-4000-8000-000000000006","timestamp":"2026-10-01T10:00:08.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-8000-000000000006","isSidechain":false,"type":"system","subtype":"turn_duration","durationMs":8000,"messageCount":7,"pendingBackgroundAgentCount":0,"timestamp":"2026-10-01T10:00:08.100Z","uuid":"00000000-0000-4000-8000-000000000007","isMeta":false,"userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"type":"ai-title","aiTitle":"Fix stale fixture date","sessionId":"11111111-2222-4333-8444-555555555555"}
{"type":"last-prompt","lastPrompt":"why does the test fail?","leafUuid":"00000000-0000-4000-8000-000000000007","sessionId":"11111111-2222-4333-8444-555555555555"}
{"type":"pr-link","sessionId":"11111111-2222-4333-8444-555555555555","prNumber":7,"prUrl":"https://github.com/example/proj/pull/7","prRepository":"example/proj","timestamp":"2026-10-01T10:00:09.000Z"}
{"type":"file-history-snapshot","messageId":"00000000-0000-4000-8000-000000000001","snapshot":{"messageId":"00000000-0000-4000-8000-000000000001","trackedFileBackups":{},"timestamp":"2026-10-01T10:00:00.000Z"},"isSnapshotUpdate":false}
{"parentUuid":"00000000-0000-4000-8000-000000000007","isSidechain":false,"type":"user","message":{"role":"user","content":"thanks"
Subagent, and its sidecar agent-a0b1c2d3e4f5a6b7c.meta.json:
.claude/projects/-srv-proj/11111111-2222-4333-8444-555555555555/subagents/agent-a0b1c2d3e4f5a6b7c.jsonl
{"parentUuid":null,"isSidechain":true,"agentId":"a0b1c2d3e4f5a6b7c","promptId":"0c0c0c0c-0000-4000-8000-000000000001","type":"user","message":{"role":"user","content":"Find where the fixture date is set."},"uuid":"00000000-0000-4000-9000-000000000001","timestamp":"2026-10-01T10:00:04.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555","version":"2.1.289","gitBranch":"main"}
{"agentType":"general-purpose","description":"Find the fixture date","toolUseId":"toolu_02","model":"claude-fable-5-1","spawnDepth":1,"requestShape":"agent","requestNonInteractive":false}
History .claude/history.jsonl:
{"display":"why does the test fail? [Pasted text #1 +3 lines]","pastedContents":{"1":{"id":1,"type":"text","contentHash":"0123456789abcdef"}},"timestamp":1790848800000,"project":"/srv/proj","sessionId":"11111111-2222-4333-8444-555555555555"}
Expected rows from the session sample with the parser: user (line 2); nothing for the empty
thinking block (line 3, even with include_thinking); tool_use Bash toolu_01 text
cd /srv/proj && npm test (line 4, from wireToolInputs); tool_result toolu_01 text
1 failing (line 5); system prompt queued: delivered at line 7 (line 6, at 10:00:05.500);
user also check the linter (line 7); assistant (line 8); system
turn_duration: 8000 ms, 7 messages (line 9); system session title: Fix stale fixture date
(line 10, timestamp of line 9); system pr-link: https://... (line 12); and the bad-line system
row (line 14). All have session_id 11111111-...; all but the bad-line row have project_path
/srv/proj and git_branch main (checked by running the parser on this sample). The subagent
sample yields two system rows on line 1, subagent a0b1c2d3e4f5a6b7c of 11111111-... and
subagent task: Find where the fixture date is set., with the parent's session_id. Noise the
parser must not want: .claude/settings.json, the .meta.json sidecar, tool-results/*.txt (read
only through a stub).
Records in the 2.1.309 shapes of the Windows round, values synthetic (a VS Code extension prompt with its environment snapshot, a one-time approval, a Remote Control bridge line and its status, a daemon handover):
{"parentUuid":null,"isSidechain":false,"type":"attachment","attachment":{"type":"environment","changes":{},"snapshot":{"workingDirectory":"/srv/proj","platform":"linux","shell":"bash","osVersion":"6.8","isGitRepo":true,"isWorktree":false,"additionalWorkingDirectories":[],"scratchpadDirectory":"/tmp/claude-1000/-srv-proj/22222222-3333-4444-8555-666666666666/scratchpad"}},"uuid":"00000000-0000-4000-a000-000000000001","timestamp":"2026-10-23T16:03:24.000Z","userType":"external","entrypoint":"claude-vscode","cwd":"/srv/proj","sessionId":"22222222-3333-4444-8555-666666666666","version":"2.1.309","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-a000-000000000001","isSidechain":false,"type":"user","message":{"role":"user","content":"run the tests"},"uuid":"00000000-0000-4000-a000-000000000002","timestamp":"2026-10-23T17:00:55.000Z","permissionMode":"auto","origin":{"kind":"human"},"promptSource":"sdk","turnOrigin":"human","sandboxed":false,"userType":"external","entrypoint":"claude-vscode","cwd":"/srv/proj","sessionId":"22222222-3333-4444-8555-666666666666","version":"2.1.309","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-a000-000000000003","isSidechain":false,"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_11","type":"tool_result","content":"ok","is_error":false}]},"uuid":"00000000-0000-4000-a000-000000000004","timestamp":"2026-10-23T17:01:10.000Z","permissionDecision":{"decision":"accept","source":"user_temporary","grantScope":"once","reasonCode":"other"},"toolUseResult":{"stdout":"ok","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false},"sourceToolAssistantUUID":"00000000-0000-4000-a000-000000000003","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"22222222-3333-4444-8555-666666666666","version":"2.1.309","gitBranch":"main"}
{"type":"bridge-session","bridgeSessionId":"cse_0123456789abcdef","lastSequenceNum":3,"ownerAccountUuid":"00000000-0000-4000-b000-000000000001","ownerOrganizationUuid":"00000000-0000-4000-b000-000000000002","sessionId":"22222222-3333-4444-8555-666666666666"}
{"parentUuid":"00000000-0000-4000-a000-000000000004","isSidechain":false,"type":"system","subtype":"bridge_status","content":"Remote Control connected","url":"https://claude.ai/code/session_0123456789abcdef","isMeta":false,"level":"info","uuid":"00000000-0000-4000-a000-000000000005","timestamp":"2026-10-23T17:02:00.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"22222222-3333-4444-8555-666666666666","version":"2.1.309","gitBranch":"main"}
{"type":"continued-in","continuedInSessionId":"33333333-4444-4555-8666-777777777777","sessionId":"22222222-3333-4444-8555-666666666666","timestamp":"2026-10-23T17:03:00.000Z"}
They yield, in the forms of the coverage table: session surface: claude-vscode (line 1) and
session surface: claude-vscode -> cli (line 3 carries another value);
scratch directory: /tmp/claude-1000/-srv-proj/22222222-.../scratchpad (session scratchpad, from the
environment attachment) (line 1); the user row and then
prompt trigger: promptSource=sdk turnOrigin=human (line 2); the tool_result row and
permission decision: accept source=user_temporary reason=- {"decision":"accept","grantScope":"once","reasonCode":"other","source":"user_temporary"}
(line 3); remote control: bridge session cse_0123456789abcdef lastSequenceNum=3 and
session synced: remote control bridge cse_0123456789abcdef url https://claude.ai/code/session_0123456789abcdef
(line 4; no bridge_status line precedes it, so the file's first, line 5, gives the url); the
bridge_status system row (line 5); and
session continued in: 33333333-4444-4555-8666-777777777777 (line 6). The surface change is a
fixture device, not something a real file was seen to do.
Records in the 2.1.310 shapes of the macOS round, values synthetic: a refusal with no decision
record and a /loop firing in
.claude/projects/-srv-proj/44444444-5555-4666-8777-888888888888.jsonl,
{"parentUuid":"00000000-0000-4000-c000-000000000001","isSidechain":false,"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_21","type":"tool_result","content":"rejected","is_error":true}]},"toolDenialKind":"user-rejected","sourceToolAssistantUUID":"00000000-0000-4000-c000-000000000001","uuid":"00000000-0000-4000-c000-000000000002","timestamp":"2026-10-26T16:25:19.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"44444444-5555-4666-8777-888888888888","version":"2.1.310","gitBranch":"main"}
{"parentUuid":"00000000-0000-4000-c000-000000000002","isSidechain":false,"type":"system","subtype":"scheduled_task_fire","content":"Running scheduled task","cron":"* * * * *","prompt":"echo hi","taskId":"task01","isMeta":false,"level":"info","uuid":"00000000-0000-4000-c000-000000000003","timestamp":"2026-10-26T17:20:00.000Z","userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"44444444-5555-4666-8777-888888888888","version":"2.1.310","gitBranch":"main"}
a prompt from claude.ai to a session a Remote Control server spawned, in
.claude/projects/-srv-proj/55555555-6666-5777-8888-999999999999.jsonl, with its
55555555-6666-5777-8888-999999999999/ccr-tip.json and the project's bridge-pointer.json,
{"parentUuid":null,"isSidechain":false,"type":"user","message":{"role":"user","content":"list the files here"},"uuid":"00000000-0000-4000-d000-000000000001","timestamp":"2026-10-26T17:10:53.000Z","permissionMode":"default","origin":{"kind":"human"},"promptSource":"sdk","turnOrigin":"human","userType":"external","entrypoint":"sdk-cli","cwd":"/srv/proj","sessionId":"55555555-6666-5777-8888-999999999999","version":"2.1.310","gitBranch":"main"}
{"eventId":"00000000-0000-4000-e000-000000000001","updatedAt":"2026-10-26T17:11:00.000Z"}
{"environmentId":"env_0123456789abcdefghijkl","pid":15081,"procStart":"1234567","sessionId":"0123456789abcdef0123456789abcdef","source":"standalone"}
and a replayed cloud prompt in a teleported session,
.claude/projects/-srv-proj/66666666-7777-4888-8999-aaaaaaaaaaaa.jsonl:
{"parentUuid":null,"isSidechain":false,"type":"user","message":{"role":"user","content":"describe this repository"},"uuid":"00000000-0000-4000-f000-000000000001","timestamp":"2026-10-26T17:13:40.000Z","permissionMode":"default","origin":{"kind":"human"},"promptSource":"sdk","remoteSourced":true,"userType":"external","entrypoint":"cli","cwd":"/srv/proj","sessionId":"66666666-7777-4888-8999-aaaaaaaaaaaa","version":"2.1.310","gitBranch":"main"}
They yield, in the forms of the coverage table: the tool_result row and
permission decision: reject (interrupted) toolDenialKind=user-rejected;
scheduled task fired: task01 cron=* * * * *;
session origin: remote (Remote Control, entrypoint sdk-cli) before the claude.ai prompt's rows;
session synced: remote event 00000000-0000-4000-e000-000000000001 at 2026-10-26T17:11:00.000Z
(from <path>, file mtime);
remote control: environment env_0123456789abcdefghijkl session 0123456789abcdef0123456789abcdef
source=standalone (from <path>, file mtime); and
session origin: remote (teleport, remoteSourced) before the replayed prompt's rows.
Forked subagents¶
A forked subagent's file begins with a copy of the forking agent's context (observed in the 4 fork
files: the records before the <fork-boilerplate> one have new uuids, and their tool ids are in
no session file), so no boundary ties those records to their source; their rows are kept, the first
user record stays user, and the <fork-boilerplate> block is the subagent task row.
Persisted output files¶
When a tool_result text starts with <persisted-output>, the parser takes the path from the
line's toolUseResult.persistedOutputPath when that key is set (2.1.309), else the path after
Full output saved to: , keeps its last component and requires the component before tool-results
to equal the transcript's session directory (<session id>/ beside a session file, the directory
above subagents/ for a subagent file). It reads <session dir>/tool-results/<name> when neither
the file, tool-results/ nor the session directory is a symlink, and otherwise keeps the stub
followed by [persisted output not found: tool-results/<name>]. On the observed install every stub
resolved.
Confidence¶
- High (bundle literals plus docs, layout confirmed locally and on Windows 2.1.309): config-dir
resolution, directory and file name sets, the
projects/key scheme and transcript naming, keychain service construction,.credentials.json, managed paths, native installer paths. - High (bundle, corroborated by claude-mem source):
.claude.jsoninside a relocated config home; and that claude-code-router, claude-mem and claude-squad keep nothing at their roots that the.claude-*/lines collect (source at the linked commits). - High (observed and consistent with the bundle): the record type list, the transcript envelope,
one block per
assistantrecord, thetool_use/tool_resultjoin,toolUseResult,sourceToolAssistantUUID,wireToolInputs/wireIngestContextand the strippedcdprefix,user-record origins and the subagent first-line prompt, the subagent join through.meta.json, millisecond ISO timestamps, epoch-millisecond history, empty thinking with signature,pr-linkre-append,queued_commandprompts before 2.1.309. - High (observed on macOS and Linux with 2.1.295 and 2.1.296, and on Windows with 2.1.309): the
sessions/<pid>.jsonregistry keys,requestedModel, and thepermission-moderecords; the permission configuration key names (observed by name only). - High (observed on Windows 2.1.309, key names and enum values only, one install): the request
capture lines, the
permissionDecisionforms, compaction, the session daemon's handover and files, the Remote Control records, the VS Code extension's sessions, the Windows temp root and CLI cache directory, and what a/clearor a removed project leaves. - High (observed on Linux 2.1.310, key names, enum values and file modes only, one install): the
native installer's layout and the version lock, the Linux CLI cache directory and temp root,
the transcript record set, the
permissionDecisionforms, the detached-headgitBranch, the twopastedContentsforms, the GitHub connection key, and what the retention sweep removed and kept in its one observed run. - High (observed on macOS 2.1.310, key names, enum values, file modes and keychain item names
only, one install): the keychain service string and account of a desktop-launched sign-in and
the file of an ssh-launched one, the native installer's layout, the macOS temp root and the
per-process sockets, the URL handler bundle, the refusal recorded without a decision, the
compaction keys, the server form of Remote Control, a cloud session and its teleport,
/loop, and what an exit,/clear,/logout, a removed project and a reboot leave. - Medium: contents of
storage-v2,remoteandsystemd(names only), ofseed-adminbeyond its being created empty byclaude --cloud, and of~/.cache/claude/stagingbeyond its being empty whenever listed. - Medium (bundle only, not exercised):
api_error,memory_saved,relocated,ended-by-model,content-replacement,frame-link,summary,progress, nested subagent subdirectories; the cloud-session files the bundle names (.session_ingress_token,remote/,server-sessions.json,remote-settings.json), which a cloud session created and teleported on macOS did not write. - Medium (vendor documentation only, not exercised): what
claude purgedeletes and leaves, and the sweep's rules forfile-history/checkpoints, auto memory and Claude Desktop transcripts. - Not determined: how common config-home names other than
.claude-<name>are; other.claude-prefixed tools beyond the three checked; whether.claude.jsonsuffix variants (.claude<suffix>.json) appear on normal installs; size ofdump-promptsin practice. - Not determined: the meaning of a
session_idthat differs fromsessionId, and whether it marks the lines the daemon handover copies; the shape offork-context-ref,content-replacement.replacements[]andhistory-suppression; whetherredacted_thinking,server_tool_useorimageblocks still appear in 2.1.x transcripts;gitBranchoutside a repository; the layout before 2.1.286. - Not determined on Windows 2.1.309: what triggered the daemon handover and
what ended the daemon; whether a prompt sent through
claude agentswas answered, since noassistantline followed it; whether claude.ai keeps the transcript of a session shared with the in-session/remote-controlafter the detach (the server form's session stayed listed on macOS; thesession synced:row of the in-session form assumes it only while the bridge is up); the meaning of the two-characterkindof a worker's registry file;defaultModefor a mode kept as the default, which no step chose. - Not determined on Linux 2.1.310: the exact trigger of the retention sweep (the observed
condition, a session with at least one prompt, is under
What survives a delete), and so whether a host whose sessions are all
short or promptless ever sweeps; whether the Linux build ever hands a session to the session
daemon, which no step of the round did; which part of the CLI owns the Bun module
/tmp/.bun-<uid>-<hex>.nodeand whether it is reused across starts; whether thebash-edit-diff/snapshots of a session whose process died stay until a reboot or the sweep; whether thefile-historybackup's modification time differs from Windows by platform or by version; why/modelwas not ahistory.jsonlline on Linux 2.1.310 when it was on Windows 2.1.309 and on macOS 2.1.310; whereghkeeps the token on a headless Linux host; and whether the Linux build writes the per-process socket that macOS keeps in/tmp/cc-socks/, and where (/tmpheld none, and$XDG_RUNTIME_DIRwas not surveyed). - Not determined on macOS 2.1.310: whether the environment id the Remote Control server printed
as
nv_<22 chars>is the file's 28-characterenv_environmentIdcut by the terminal, since the round did not compare them. - Not determined on Windows 2.1.309 and Linux 2.1.310: whether a paste in a prompt is wrapped in
<pasted_content>as on macOS 2.1.310, which neither round recorded. - Not determined on macOS 2.1.310: why every refusal there took the form with no decision record,
while Linux 2.1.310 wrote
userFeedbackfor a refusal with a reason (the macOS console ran inscreenwithTERM=screen, so a terminal difference is not ruled out); whether the retention run in the session started withcleanupPeriodDays0 was paused by the settings error or found nothing older than 30 days, and what the sweep removes there, which rests on the vendor's page; whether a process without access to the login keychain other than an ssh-launched one falls back to.credentials.json; and whatappend-system-prompt.txtsays, which was not read.