I've been using Claude Code heavily for the past few weeks—building features, debugging production issues, refactoring codebases. One thing that struck me was how much local state the tool maintains on disk. It's not a stateless CLI that fires requests and forgets. There is a well-organized filesystem sitting at ~/.claude that powers session continuity, project memory, debug tracing, and more.
I decided to explore this directory the way I'd explore any system I depend on: by reading the tree, understanding the data model, and mapping out how the pieces connect.
The Top-Level Layout
Here is what the ~/.claude directory looks like on my machine:
~/.claude/
├── backups/ # Automatic config backups
├── cache/ # Cached assets (changelogs, etc.)
├── debug/ # Full session debug logs
├── downloads/ # Downloaded artifacts
├── file-history/ # Per-session file edit history
├── history.jsonl # Global prompt history
├── ide/ # IDE integration lock files
├── mcp-needs-auth-cache.json
├── paste-cache/ # Clipboard paste snapshots
├── plans/ # Plan-mode markdown files
├── plugins/ # Plugin registry and marketplace data
├── projects/ # Per-project session data and memory
├── session-env/ # Per-session environment snapshots
├── settings.json # Global user settings
├── shell-snapshots/ # Shell environment captures
├── tasks/ # Task tracking state (todo lists)
└── todos/ # Per-session todo persistence
This is a surprisingly rich structure for a CLI tool. Let's walk through each piece.
projects/ — The Project Memory System
This is the most interesting directory. Claude Code maintains per-project state by encoding the absolute path of each project directory into a folder name, replacing path separators with dashes:
~/.claude/projects/
├── -Users-dp791e-Documents-projects-qrtrack/
├── -Users-dp791e-Documents-projects-claude-code-usage-meter/
├── -Users-dp791e--claude/
└── -Users-dp791e/
Each project directory contains .jsonl files named after session UUIDs. These are full conversation transcripts—every message exchanged between you and the agent during a session. The project directory can also contain a memory/ subdirectory with a MEMORY.md file, which acts as persistent memory the agent loads at the start of every conversation in that project.
This design means that if you open Claude Code in ~/Documents/projects/qrtrack, it automatically scopes to the right project folder and loads prior context. The path-encoding scheme is simple but effective—no database, no registry, just filesystem naming conventions.
debug/ — Full Session Traces
Every session gets a debug log. These are large text files (some reaching several megabytes) named by session UUID:
~/.claude/debug/
├── 0cf21a6b-663a-477f-9768-39a3a6e2fc27.txt (68 KB - current)
├── 97bbd620-ce27-40d2-96d7-b07987c3b221.txt (7.2 MB)
├── bdad57c0-2319-420d-a83d-15083fd64044.txt (3.4 MB)
└── latest -> 0cf21a6b-...txt (symlink)
The latest symlink always points to the currently active session's debug log. This is a nice touch—you can always tail -f ~/.claude/debug/latest to watch what the agent is doing in real time. A long-running session where the agent modifies dozens of files and runs shell commands can easily produce multi-megabyte traces.
file-history/ — Edit Provenance Tracking
Each session that edits files gets its own directory under file-history/. Inside, you'll find snapshots of files before and after the agent modified them:
~/.claude/file-history/
├── 97bbd620-ce27-40d2-96d7-b07987c3b221/ (110 files)
├── 88dc812b-a7ad-41c6-abf0-c454740a0884/ (72 files)
├── bdad57c0-2319-420d-a83d-15083fd64044/ (69 files)
└── ...
This is essentially a per-session undo log. If the agent makes a bad edit, you can recover the original version from this directory. The count of files per session also gives you a rough measure of how much the agent modified during that conversation—some of my sessions touched over 100 files.
tasks/ — Structured Task Management
Claude Code has a built-in task tracking system. Each task set lives in its own UUID directory:
~/.claude/tasks/97be485d-324b-4be0-95ee-7fa8ce9d3c84/
├── .lock
├── .highwatermark
├── 1.json
├── 2.json
├── 3.json
├── ...
└── 13.json
Each numbered JSON file represents an individual task with status (pending, in_progress, completed), description, dependencies, and ownership. The .lock file prevents concurrent writes, and .highwatermark tracks the next available task ID. This is a lightweight, file-based task queue—no SQLite, no external service, just JSON files and filesystem-level locking.
history.jsonl — The Global Prompt Log
Every prompt you type into Claude Code is appended to history.jsonl. Each line is a JSON object with the display text, a timestamp, the project path, and the session ID:
{"display":"understand the current repo, plan to rewrite in react",
"pastedContents":{},
"timestamp":1771736038190,
"project":"/Users/dp791e/Documents/projects/qrtrack",
"sessionId":"fc5d53c1-de75-4e0a-b0b7-fe377af959a0"}
This is a classic append-only log pattern. JSONL (newline-delimited JSON) is a smart choice—it's trivially appendable, doesn't require parsing the whole file to add an entry, and tools like jq can slice and filter it easily. The pastedContents field captures any clipboard content that was included with the prompt.
backups/ — Configuration Safety Net
Every time the internal configuration is modified, a timestamped backup is created:
~/.claude/backups/
├── .claude.json.backup.1771940140437
├── .claude.json.backup.1771940203103
├── .claude.json.backup.1771940458928
└── .claude.json.backup.1771946176535
The timestamps are Unix epoch milliseconds. This gives you a rollback path if a configuration change breaks something. It's a simple pattern but one that many tools skip.
plans/ — Architectural Planning Documents
When you use Claude Code's plan mode, the agent writes its implementation plan to a markdown file here. The filenames are randomly generated word combinations:
~/.claude/plans/
├── tingly-weaving-kite.md
├── elegant-forging-snail.md
└── iridescent-zooming-kettle.md
These are human-readable plan documents that you can review, diff, or share. Using memorable random names instead of UUIDs is a nice usability choice—you can actually tell these files apart in a listing.
shell-snapshots/ — Reproducible Environments
When a session starts, Claude Code captures the state of your shell environment:
~/.claude/shell-snapshots/
├── snapshot-zsh-1771767452217-jx1wkp.sh
├── snapshot-zsh-1771792431238-4ei00c.sh
└── snapshot-zsh-1771946155112-w9uztf.sh
These are full shell initialization snapshots (~50KB each). They capture environment variables, PATH, shell options—everything needed to reproduce the exact shell context the agent was operating in. This matters because if a build command fails, you want to know whether the environment was different, not just what the command was.
The Supporting Cast
A few smaller directories round out the architecture:
- paste-cache/ — When you paste content into a prompt, it gets cached here as a text file keyed by a content hash. This deduplicates repeated pastes and lets the history log reference pasted content without inlining it.
- session-env/ — Per-session environment directories (currently empty on my machine), likely used for session-specific temporary state.
- ide/ — Lock files for IDE integrations (VS Code, Cursor, etc.), using PID-based naming to prevent multiple instances from conflicting.
- plugins/ — Plugin system metadata including a blocklist, known marketplace URLs, and cached marketplace data.
- todos/ — Per-session JSON files for the agent's todo tracking, keyed by a composite of session ID and agent ID.
- cache/ — General-purpose cache (contains a cached changelog on my machine).
- settings.json — Global user preferences. On a fresh install, this is just
{}.
Design Patterns Worth Noting
Several architectural decisions stand out:
Everything is files. There's no embedded database, no SQLite, no LevelDB. Just JSON files, JSONL logs, markdown documents, and directories. This makes the system inspectable, debuggable, and portable. You can cp -r ~/.claude to another machine and it just works.
UUIDs as primary keys. Sessions, tasks, and debug logs are all keyed by UUIDs. This avoids collisions without coordination—critical for a tool that can spawn sub-agents in parallel.
Path-encoded project scoping. Instead of maintaining a project registry, the tool encodes the filesystem path into the directory name. No lookup table, no config file mapping project names to paths. The filesystem is the index.
Append-only logs. History and session transcripts use JSONL, which is safe for concurrent appends and doesn't require reading the whole file to write an entry.
Symlinks for recency. The debug/latest symlink is a zero-cost way to always know which session is current without scanning modification times.
Lock files for concurrency. Tasks use .lock files, IDE integrations use PID-based lock files. Simple, portable, no external dependencies.
What This Tells Us About the Agent's Architecture
Looking at the filesystem as a whole, you can see that Claude Code is designed as a stateful, session-aware agent rather than a thin API wrapper. It maintains full edit provenance, remembers project context across conversations, tracks its own task progress, and captures enough environmental context to debug failures after the fact.
The choice to use the filesystem as the storage layer—rather than a database—reflects a Unix philosophy: transparent, composable, inspectable. Every piece of state is a file you can read, grep, diff, or delete. There's no migration step, no schema evolution problem, and no corruption risk from a crashed embedded database.
For a tool that sits between an AI model and your codebase, this level of transparency is exactly right.
~ Comments & Discussion ~
Have thoughts on this post? Join the discussion below! Comments are powered by Disqus.