CLAUDE.md Is a System Prompt, Not a Readme: Understanding the .claude/ Folder
Source: hackernews
The .claude/ folder is easy to dismiss as boilerplate. It appears alongside your source code, grows quietly in your home directory, and most developers interact with it only through Claude Code’s higher-level commands without ever opening a file directly. A recent piece on Daily Dose of DS surfaced this topic on Hacker News and collected 274 points and 138 comments, suggesting the community has more interest in the underlying mechanics than the tooling’s surface area might imply.
Understanding the full structure changes how you configure projects, what you commit to version control, and what risks you accept without realizing it.
Two Directories, One System
Claude Code maintains two separate .claude/ locations. The global one lives at ~/.claude/ and holds your personal preferences, your cross-project memory file, and conversation history for every project you’ve worked on. The project-level one lives at <project-root>/.claude/ and holds repository-specific settings that override the global ones.
The merge order is: global ~/.claude/settings.json forms the base, then project .claude/settings.json overrides that, then .claude/settings.local.json provides machine-specific overrides on top. Anthropic adds a fourth level for enterprise deployments, where organization policy can lock down model choice or disable tools entirely regardless of what individual users configure.
This hierarchy mirrors the pattern of .editorconfig, .eslintrc, and similar tools: separate what belongs to the project from what belongs to the developer from what belongs to a specific machine. It is a sensible design, and the fact that every layer is a plain JSON or Markdown file makes the whole system auditable in ways that a proprietary binary format would not be.
CLAUDE.md Is Injected Into the System Prompt
The most consequential file in the entire system is CLAUDE.md. Anthropic describes it as a persistent memory mechanism, but that framing undersells what it does. Its contents are injected directly into the system prompt at session start. Every instruction you put there is present in every conversation you have in that project, exactly as if you had typed it manually at the beginning of each session.
Claude Code walks up the directory tree from the current working directory and loads every CLAUDE.md it finds, including one at ~/.claude/CLAUDE.md for global personal instructions. All of them are concatenated and included. In a monorepo, you can have a root-level file for project-wide context and per-package files that activate only when you work within a specific subdirectory, because Claude Code loads the files relevant to your current working directory.
This makes CLAUDE.md load-bearing in a precise way:
## Architecture
- API: Node.js/Express on port 3001
- Frontend: React/Vite on port 5173
- Database: PostgreSQL via Prisma ORM
## Code conventions
- TypeScript strict mode throughout
- Use `const` over `let` by default
- All async functions require explicit error handling with try/catch
## Commands
- `npm run dev` — start both services
- `npm run test` — run Vitest suite
- `npm run db:migrate` — apply pending migrations
## Never
- Commit any file matching .env*
- Modify migration files that have already been applied
A well-maintained file like this eliminates repeated instructions entirely. Claude Code also has a /init command that generates an initial CLAUDE.md by having the model analyze your codebase, and a /memory command that opens the file for editing mid-session. The model itself can append to CLAUDE.md via tool calls when it determines something is worth persisting across sessions.
Because CLAUDE.md is plain Markdown committed to the repository, it also functions as a form of institutional documentation. New contributors who use Claude Code get the same accumulated context automatically, without needing to build it up conversation by conversation.
One important maintenance consideration: CLAUDE.md degrades silently if the architecture it describes drifts from reality. Stale context can produce more confident but incorrect suggestions than no context at all. Keeping it updated as the project evolves is part of the maintenance burden, not a one-time setup task.
The Permission Model in settings.json
The settings.json file handles more than model selection. Its permissions field gives you fine-grained control over what Claude Code is allowed to do:
{
"permissions": {
"allow": [
"Bash(git *)",
"Bash(npm run *)",
"Read(**)",
"Write(src/**)"
],
"deny": [
"Bash(rm -rf *)",
"Write(.env*)"
]
}
}
The format is ToolName(glob-pattern). Supported tools include Bash, Read, Write, Edit, Glob, Grep, WebFetch, and WebSearch. Rules in deny take precedence over allow when both match a given operation.
For CI/CD usage or shared development environments, this is genuinely useful. Committing a settings.json that restricts Claude to read-only file access and a narrow set of safe commands means any automated agent run operates within an explicit policy that travels with the code. Without it, Claude Code falls back to interactive permission prompts, which do not work in non-interactive pipelines.
.mcp.json and the Extension Point
The Model Context Protocol (MCP) is Anthropic’s open standard for extending Claude with external tools and data sources via JSON-RPC. Each project defines its MCP servers in a .mcp.json file at the project root, outside the .claude/ directory:
{
"mcpServers": {
"my-tools": {
"command": "node",
"args": ["dist/mcp/server.js"]
}
}
}
Claude Code starts each configured server as a subprocess on session startup and communicates with it over stdio. Remote servers using Server-Sent Events are also supported via a url field for hosted MCP services. Global MCP servers defined in ~/.claude/settings.json are available across all projects; project-level ones are scoped to that repository.
The .mcp.json file is normally committed to version control, which means MCP server configuration is reproducible and shared across the team. The risk is the env field: credentials placed there are committed alongside the server definition. References to environment variables are the safer pattern.
Conversation Storage and Its Implications
Every conversation lives in ~/.claude/projects/ as a JSONL file, one JSON object per line. The directory name is a hash of the absolute project path. Each line represents a turn or event:
{"type":"user","message":{"role":"user","content":"explain this function"},"uuid":"abc-123","timestamp":"2025-04-10T14:23:01.000Z"}
{"type":"tool_use","tool":"Bash","input":{"command":"git log --oneline -5"},"uuid":"ghi-789"}
{"type":"tool_result","tool":"Bash","output":"a1b2c3 fix bug\n...","parentUuid":"ghi-789"}
Tool results are stored verbatim. The full output of every bash command, every file Claude read, every API response returned through an MCP tool: all of it lands in these files. For heavy users, the ~/.claude/projects/ directory grows into gigabytes without cleanup. The cleanupPeriodDays setting in settings.json controls retention.
When a conversation grows long enough to approach the context window, Claude Code inserts a summary event: it asks the model to summarize the conversation so far, stores that as a synthetic message, and drops the earlier raw turns. The JSONL file retains the compressed version. The /resume command reads these files to reconstruct a past session’s context.
The format is simple and parseable with standard tooling, which means you can audit your own history, analyze tool usage patterns, or extract cost data from the costUSD fields on assistant turns. That transparency is worth something. The tradeoff is that everything is plaintext and unencrypted at rest. On a shared machine, or after any unauthorized filesystem access, all past sessions are readable without any additional credentials.
The Security Considerations Worth Understanding
Several aspects of this design have security implications that the tooling does not surface prominently.
The apiKeyHelper field in settings.json specifies a script path that Claude Code executes to retrieve an API key. A committed project-level settings.json with a malicious apiKeyHelper path is a code execution vector: anyone who opens a cloned repository in Claude Code would have that script run with their full user permissions. This is not a theoretical concern; it is a straightforward supply chain attack surface for any project that commits Claude Code configuration.
The bypassPermissionsModeAccepted flag, when set to true in a committed settings.json, causes Claude Code to skip all interactive permission prompts for anyone who opens the project. The behavior is silent on startup, with no prominent warning that the safety layer has been bypassed. A developer cloning a repository with this flag set might not realize Claude Code is operating without its usual gates.
Tool results stored in JSONL history can accumulate sensitive data: environment variable dumps, database query results, API responses containing tokens. Developers who would never commit a secret to git may not apply the same scrutiny to what they run during a Claude Code session, since the command output feels transient even though it is being persisted locally.
Using the Full Surface Area
A few patterns hold up in practice once you understand the full structure.
Treat CLAUDE.md as a first-class project artifact with its own maintenance cycle, not as a one-time notes file. Outdated instructions are worse than missing ones because they produce confident, wrong behavior.
Commit a settings.json with explicit permissions.allow and permissions.deny rules for any project where Claude Code might be used in automated or less-supervised contexts. Do not commit bypassPermissionsModeAccepted: true.
Keep credentials out of .mcp.json’s env fields. Use settings.local.json (which should be gitignored) for machine-specific values and environment variables for anything sensitive.
Review ~/.claude/projects/ periodically. The JSONL history is useful for auditing what Claude did in a session, but it accumulates over time and the cleanupPeriodDays setting exists for a reason.
The plain-file design makes every layer of Claude Code’s configuration inspectable and version-controllable. That is the right call for a tool that developers will integrate into their workflows and share across teams. Getting familiar with the actual structure removes the opacity that makes any developer tool feel unpredictable.