Navigate the Source: Understand, Predict & Influence Claude

Most people treat Claude as a black box. You aren't most people. With the source in hand, you can trace any behavior back to its exact origin, understand why Claude makes certain decisions, and know precisely where to look to change them. This guide is your map.

The Mental Model: What Claude Actually Sees

Before opening a single file, internalize this: from Claude's perspective, every conversation is a carefully assembled JSON payload sent to the API. The source code's primary job is constructing that payload. Once you understand what ends up in it, you understand Claude's behavior.

The payload has three major parts that the source assembles before every API call:

📋
System Prompt
Built by context.ts from multiple fragments: base instructions, git status, date/time, injected memory (CLAUDE.md), and tool usage guidelines. This is Claude's instruction manual — it never changes mid-turn.
🔧
Tool Schemas
Every available tool is serialized into a JSON schema with a name, description, and inputSchema. The description text is literally what Claude reads to decide when to use a tool. Tool order in the array influences selection probability.
💬
Message History
The normalized conversation: user messages, Claude's text + tool_use blocks, and tool_result blocks. The normalizeMessagesForAPI() function strips all UI-only message types before sending. What's left is exactly what Claude reasons over.
The golden rule: If you want to understand why Claude behaved a certain way, ask yourself — "What was in the payload at the time?" The source code is just a machine for building that payload.

Tracing a Behavior Back to Source

When you see Claude do something surprising, you can always trace it. Here's the playbook. Work backwards from the observable behavior:

Example: "Why does Claude always ask before running bash commands?"

1
tools/BashTool/BashTool.ts
Look at checkPermissions(). BashTool does not declare isReadOnly: () => true, so buildTool()'s fail-closed defaults kick in. Every bash invocation routes to the permission system.
2
hooks/useCanUseTool.tsx
This hook evaluates the current permissionMode from AppState. In default interactive mode it calls showPermissionDialog() for non-read-only tools.
3
utils/permissions/
The permission rules live here. You'll find the allow-list for commands that auto-approve (like ls, cat, git status) vs those that always ask.
4
Levers: --auto flag, settings.json permissionMode
To suppress the dialog: run with --auto (still prompts on destructive ops) or --dangerously-skip-permissions (no prompts at all). Both modify the permissionMode field in AppState before the first query.

Example: "Why does Claude 'remember' things I told it in a previous session?"

1
memdir/
On startup, Claude Code scans for CLAUDE.md files starting at the current directory and walking up to the root. It also checks ~/.claude/CLAUDE.md for global preferences.
2
context.ts → getUserContext()
The loaded CLAUDE.md contents are assembled into AttachmentMessage objects and prepended to the conversation as injected system context — not as user messages, but as a special attachment layer that Claude always sees.
3
Lever: Write to CLAUDE.md
Anything you put in CLAUDE.md at your project root (or ~/.claude/CLAUDE.md globally) will be injected into every session's context. This is the most powerful persistent influence mechanism available.

Example: "Why does Claude reach for Grep before FileRead when searching?"

1
tools.ts → getAllBaseTools()
The tool list order matters. GlobTool and GrepTool appear before FileReadTool in the array. When this array is serialized to the API, earlier tools appear more prominently in the tool list, and Claude statistically prefers tools listed earlier for ambiguous tasks.
2
tools/GrepTool/GrepTool.ts → description string
The description says something like: "Search file contents using regex. Use this to find function definitions, variable usages, import statements." Claude reads this literally — it uses Grep because its description matches the intent of "find something in code".

Tool Descriptions: Claude's Vocabulary

This is the single most actionable insight in the entire codebase: the description string in each tool file is literally what Claude reads to decide when to use that tool. It is not documentation. It is a behavioral instruction.

Every tool exports a description property (or its buildTool() config contains one). When the API payload is built, these descriptions are serialized verbatim into the tool schemas sent to Claude. So when you wonder "why did Claude use BashTool for this instead of FileReadTool?" — read both descriptions side by side and ask which one better matches what you asked for.

Tool Key phrase in its description (what triggers it) What it implies for your prompts
BashTool "execute commands", "run scripts", "install packages", "compile" Say "run", "execute", "install", "compile" to trigger BashTool. Say "read", "show me" to avoid it.
FileEditTool "make changes to existing files", "surgical replacement" Explicitly saying "edit line X" or "change this function" routes to FileEditTool. "Replace the whole file" routes to FileWriteTool.
GrepTool "search file contents", "find definitions", "locate usages" Asking Claude to "find where X is used" will reliably trigger Grep before any file read.
AgentTool "spawn a subagent", "parallel tasks", "delegate work" Phrasing like "do this in parallel" or "handle each file independently" may trigger AgentTool spawning sub-agents.
EnterPlanModeTool "before making changes, create a plan", "risky operation" Claude can self-trigger plan mode when it perceives high risk. You can also ask "make a plan first" to push it there explicitly.
AskUserQuestionTool "when you need clarification", "ambiguous requirements" Claude uses this when your prompt genuinely lacks necessary information. Being vague = more AskUserQuestion calls. Be specific to minimize interruptions.
WebFetchTool "fetch content from URL", "read documentation", "access web page" Include a URL in your request and Claude will almost always use WebFetch, even if you didn't explicitly ask for it.
How to find any tool's description: Open src/tools/[ToolName]/[ToolName].ts in the source repository and look for the description field in the buildTool() call. The exact text there is what Claude reads. If you want Claude to prefer one tool over another, match your phrasing to that tool's description language.

CLAUDE.md: Your Direct Line to Every Session

The most underused power-user feature in Claude Code is also the simplest. CLAUDE.md files are Markdown documents that get injected as context into every single session that runs in their directory. They are loaded by memdir/, assembled by context.ts → getUserContext(), and prepended to the message history as AttachmentMessage objects before the first API call.

Where Claude Looks (in priority order)

1. Project root ./CLAUDE.md # loaded for sessions in this project 2. Parent dirs ../CLAUDE.md # walked upward to filesystem root 3. Subdirectory src/CLAUDE.md # scoped context for that subdirectory 4. Global ~/.claude/CLAUDE.md # always loaded, every project

What to Put in CLAUDE.md

Because CLAUDE.md is injected into the system context (not as a user message), it has stronger influence than anything you type in the chat. Think of it as a standing order that never expires. The source's context.ts assembles it under a heading like # Memory in the system prompt, so Claude treats it as authoritative project context.

# Project: my-api-server

## Stack
- Node.js 20, TypeScript strict mode
- Postgres via Prisma ORM
- Vitest for tests (NOT Jest)

## Conventions
- Functions: camelCase. Files: kebab-case. Classes: PascalCase.
- Always add JSDoc to exported functions.
- Prefer `const` assertions over enums.

## What NOT to do
- Never use `any` type — use `unknown` and narrow.
- Never commit directly to main — always use feature branches.
- Never log secrets even in dev.

## Test pattern
- Co-locate tests: `src/foo.ts` → `src/foo.test.ts`
- Run tests: `npm test`

## Current focus
Working on the auth module. Most files in `src/auth/` are WIP.
Practical power moves with CLAUDE.md: You can permanently ban specific behaviors ("Never use var, always const"), set coding style rules, pin team conventions, document known gotchas in the codebase, and even specify which test runner to use. Claude respects these as authoritative because they appear in the injected context layer, not the conversation.

Feature Flags: What's Actually Running

The source uses Bun's feature() DCE macro to compile entire subsystems in or out. In the public release of Claude Code, some of these flags are on and some are off. Knowing which are which tells you what capabilities are actually available versus what's in the source but not exposed.

Flag What it enables Status in public build Where to check
KAIROS KAIROS assistant mode — an alternative interaction paradigm, different from the standard REPL Off (internal only) assistant/index.ts
VOICE_MODE Voice input/output support Off (experimental) voice/
BRIDGE_MODE Remote WebSocket bridge for cloud-hosted execution environments On (for claude.ai/code web) bridge/bridgeMain.ts
DAEMON Background daemon process for persistent agent sessions Partial (internal) entrypoints/
COORDINATOR_MODE Multi-agent coordinator that orchestrates swarms of worker agents Off (experimental) coordinator/coordinatorMode.ts
PROACTIVE Proactive task suggestions, SleepTool (Claude can pause and wait) Off tools/SleepTool/
AGENT_TRIGGERS Cron-based scheduling: ScheduleCronTool and related tools On (via Cowork) tools/ScheduleCronTool/
WORKFLOW_SCRIPTS Workflow automation: multi-step scripted agent workflows Off (experimental) commands/workflows/
REACTIVE_COMPACT Reactive context window compaction (triggered automatically) On services/compact/reactiveCompact.ts
CONTEXT_COLLAPSE More aggressive context collapsing for very long sessions Off services/contextCollapse/
EXPERIMENTAL_SKILL_SEARCH Fuzzy search for skill discovery Off skills/

The key file to read for flag usage is tools.ts — it has the most flag-conditional logic. Any feature('FLAG_NAME') ? require(...) : null pattern tells you that the following code only exists in builds where that flag is enabled.

Permission System: Predicting What Gets Blocked

Every tool call passes through a permission gate before execution. You can predict whether a given operation will need user approval by reading three boolean flags on the tool:

// These three flags on every tool determine permission behavior:
isReadOnly(input): boolean     // true  → auto-approve in most modes
isConcurrencySafe(input): boolean  // true  → can run in parallel batches
isDestructive(input): boolean  // true  → triggers EXTRA-prominent dialog

// The defaults in buildTool() are fail-closed:
// isReadOnly: false     → assumed to write, needs permission
// isDestructive: false  → not flagged as extra-dangerous
Scenario Mode Outcome
GrepToolisReadOnly: true Any ✅ Auto-approved, runs silently
GlobToolisReadOnly: true Any ✅ Auto-approved, runs silently
FileEditTool — not read-only interactive (default) ⏸ Shows diff, waits for y/n
FileEditTool — not read-only auto (--auto) ✅ Auto-approved unless path is sensitive
BashTool — risky command detected interactive ⏸ Prominent dialog with command preview
BashTool — safe command (git status, ls) interactive ✅ Auto-approved (on allow-list)
Any tool — isDestructive: true interactive or auto ⛔ Red-highlighted dialog, requires explicit confirmation
Any tool bypass (--dangerously-skip-permissions) ✅ Always auto-approved (no exceptions)
The permission allow-list lives in utils/permissions/. It contains the specific bash commands that auto-approve in interactive mode (things like git status, cat, ls, echo). If you find Claude auto-approving a command you expected it to ask about, that command is on this list.

Context Window & Compaction: Why Claude "Forgets"

Long sessions inevitably hit the context window limit. Claude Code manages this with three compaction strategies defined in services/compact/. Understanding them explains the "memory loss" behavior many users experience.

Compaction strategies (services/compact/) ├── AutoCompact # Triggered when approaching token limit │ │ Summarizes and removes oldest tool-use/result pairs │ └ Preserves user and assistant text messages ├── Microcompact # Inline: shrinks individual tool outputs │ │ Large outputs (bash stdout, file contents) replaced │ └ with summaries: "Output was 4,200 lines — summary: ..." └── Snip # Nuclear option: removes entire conversation sections Targets the oldest, least-referenced segments Only used when approaching hard context limit

What Gets Compacted First

Compaction is not random. The strategies prioritize removing tool_use / tool_result message pairs before touching actual conversational text. This is intentional: tool exchange data (file contents, bash output) is large and disposable once Claude has processed it. Your conversation messages (the human-readable exchanges) survive much longer.

The practical implication: if you showed Claude a large file early in the session, there's a good chance the file contents were compacted out after a few more turns. If you need Claude to reference that file again later, use FileReadTool (or ask Claude to re-read it) rather than assuming it still "remembers."

How to Control Compaction

TechniqueHow it works
Keep sessions focused Each task in its own session means the context never gets cluttered with irrelevant history. Claude Code's /new command starts a fresh session.
Put critical context in CLAUDE.md Since CLAUDE.md is injected fresh at the start of every turn (not stored in message history), it's immune to compaction. Anything you need Claude to always know belongs there.
Use /compact manually The /compact slash command triggers a controlled compaction + summary before the limit hits. You control the timing and get a summary of what was removed.
Avoid huge file reads early Large file contents are the first thing compacted. Read files close to when you need them, not speculatively at session start.

The System Prompt: What Claude Starts With

The system prompt is assembled fresh for every API call by context.ts and injected parts from multiple sources. Understanding its structure lets you predict what "defaults" Claude operates under and where you can inject your own instructions.

System Prompt Structure (assembled in context.ts) ├── [Base instructions] # Hardcoded in main.tsx / query.ts Role definition, general behavior guidelines ├── [Tool usage guidelines] # From tool.description + system prompt injections When/how to use each tool, ordering hints ├── [Git context] # From services/git.ts (if in a git repo) Current branch, recent commits, git status ├── [Date & environment] # Injected at call time Current date/time, OS, working directory ├── [CLAUDE.md contents] # <-- YOUR INJECTION POINT Loaded from disk, formatted under # Memory heading └── [Coordinator context] # Only in multi-agent mode Scratchpad paths, worker tool restrictions

Notice that CLAUDE.md is the only component in this list that you can control. Everything else is generated by the runtime. This reinforces CLAUDE.md as the primary lever for persistent behavioral modification.

Slash Commands vs Prompting: Know the Difference

There are two fundamentally different ways to direct Claude Code: slash commands and natural language prompts. They operate at different levels of the stack and have different strengths.

Slash Commands (/commit, /review, etc.) Natural Language Prompts
Where processed commands.tscommands/[name].ts, before the API call Inside the query loop, via API + tool dispatch
Reliability Deterministic — same code path every time Probabilistic — Claude interprets your intent
Speed Often faster — can skip API calls for simple ops Always hits the API at least once
Best for Repeated, well-defined workflows Novel, exploratory, context-sensitive tasks
Customizable Yes — skills let you add your own Via CLAUDE.md + prompt engineering

The 80+ built-in slash commands live in commands/. Key ones to know:

CommandWhat it does (from source)
/commitRuns git diff, analyzes changes, generates a conventional commit message, shows preview, lets you confirm. Includes co-author attribution by default.
/reviewPerforms a structured code review of staged or specified changes: correctness, security, performance, style.
/compactManually triggers context compaction with a user-readable summary of what was removed.
/memoryOpens the CLAUDE.md file for your current project in an editor — the fastest way to add persistent context.
/mcpManage MCP server connections: list, add, remove, auth.
/sessionSession management: list recent sessions, resume a previous one, view transcripts.
/newStarts a fresh session — clears message history but preserves CLAUDE.md context.
/autofix-prFetches a PR's CI failures and automatically applies fixes. A power-user command that chains multiple tools.
/bughunterInitiates a structured bug-finding workflow over a specified codebase area.

Bundled Skills: Ready-Made Power Commands

The skills/bundled/ directory contains 19 pre-packaged slash commands that ship with Claude Code. They're implemented as JavaScript files that get dynamically loaded as SkillTool instances. Think of them as first-class features that happen to be implemented in user-land.

SkillWhat it does and when to use it
/simplifyReviews recently changed code and suggests refactoring for clarity. Run after you've gotten something working to clean it up.
/loopRuns a prompt or command on a recurring basis within a session. Useful for "keep watching this file and tell me when X changes".
/scheduleCreates a cron-like scheduled agent trigger. The agent will run at the specified interval, independent of your active session.
/update-configModifies settings.json with proper hook support — safer than editing the file directly.
/claude-apiScaffolds a new project that uses the Anthropic SDK. A guided setup flow for building Claude-powered apps.
/review-prAI-powered PR review: fetches the diff, analyzes it for bugs, security issues, and style, and posts a structured review.

The Deferred Tools Trick: Hidden Capabilities

Several powerful tools are hidden from Claude's initial context to save tokens. They're only available after Claude calls ToolSearchTool with the right keyword. This means there are capabilities you might not know exist — until you ask for them the right way.

To trigger a deferred tool, describe what you want in a way that matches the tool's searchHint. The tool search runs fuzzy matching against these hints. Known deferred tools and their hints:

ToolSearch hint phraseHow to trigger it
ScheduleCronTool "create recurring cron scheduled agent task" Ask Claude to "set up a recurring task" or "schedule this to run every day at 9am"
BriefTool "summarize session for handoff" Ask Claude to "create a brief" or "summarize this session for another agent"
NotebookEditTool "jupyter notebook cell edit" Ask Claude to "edit my notebook" or reference a .ipynb file
LSPTool "language server diagnostics hover definition" Ask for "go to definition" or "show me where this is defined"

Behavior Cheat Sheet: Observable → Source → Lever

A quick-reference map from things you observe to where they live in the source and what you can change.

What you observe Source location How to influence it
Claude asks for permission before every file edit
hooks/useCanUseTool.tsx
utils/permissions/
Use --auto flag or add path to auto-approve list in settings
Claude "forgets" a file you showed it earlier
services/compact/autoCompact.ts
Put critical context in CLAUDE.md; it's injection-immune to compaction
Claude uses bash when you wanted a file read
tools/BashTool/BashTool.ts → description
tools.ts → tool ordering
Explicitly say "read the file" not "show me what's in" — matches FileReadTool's description language
Claude keeps suggesting the same approach you've rejected
context.ts → getUserContext()
Add the anti-pattern to CLAUDE.md: "Never suggest X. Use Y instead."
Claude spawns sub-agents you didn't expect
tools/AgentTool/AgentTool.ts → description
Add to CLAUDE.md: "Do not use AgentTool unless I explicitly ask to parallelize." Or avoid phrasing that mentions "parallel" or "independently".
Claude enters plan mode unexpectedly
tools/EnterPlanModeTool/EnterPlanModeTool.ts
The tool description says Claude should enter plan mode for "risky operations." To skip it: add "proceed without a plan unless I ask for one" to CLAUDE.md.
Claude uses a different coding style than you want
Context assembly — no style rules exist by default
Add a complete style guide to CLAUDE.md. It will be injected as authoritative context on every turn.
Claude writes tests in the wrong framework
No default test framework preference in source
Add to CLAUDE.md: "Test framework: Vitest. Never use Jest." Claude reads this as authoritative.
Claude stopped using a tool that used to work
tools.ts → feature() flags + buildTool()
Check if the tool has a feature flag that may have changed. Also verify the tool is in the getAllBaseTools() return list and not deferred.
Claude runs many tools in parallel, causing conflicts
services/tools/toolOrchestration.ts
Tools with isConcurrencySafe: false (the default) run serially. If a custom tool is running in parallel unexpectedly, it was explicitly marked safe. Check the tool definition.
Claude's git commit messages don't match your convention
commands/commit/ or skills/bundled/commit.js
Add your commit convention to CLAUDE.md, or fork the bundled commit skill and override it with your own /commit skill script.
An MCP tool is available but Claude never uses it
services/mcp/client.ts → tool discovery
The MCP tool description may not match your request language. Add an explicit mention of the MCP tool's name in your prompt, or add its use-case to CLAUDE.md.

How to Read Any Tool's Source File

When you browse the source repository and want to understand a specific tool, here's the standard reading order for any tools/[ToolName]/[ToolName].ts file:

  1. Read the description string first. This is the behavioral contract — what Claude understands about this tool's purpose. If you want to predict when Claude will use it, this is your answer.
  2. Check isReadOnly and isConcurrencySafe. These determine whether the tool auto-approves and whether multiple instances run in parallel. Two lines that tell you everything about the tool's safety profile.
  3. Read checkPermissions() if it exists. Any custom permission logic is here. For BashTool, this is where the command classifier runs.
  4. Skim the call() method. The actual execution logic. You don't need to understand every line — focus on what input it reads and what output it returns. The shape of the output determines what Claude sees as the tool's "result."
  5. Check renderToolUseMessage() and renderToolResultMessage(). These are the terminal UI renderers. They don't affect Claude's reasoning but tell you what the user sees in the REPL.
  6. Look for a shouldDefer property. If present and true, this tool is hidden from Claude's initial context and only available via ToolSearch. You'll need to use the right search phrase to access it.

Quick Reference: File → What it Controls

File / Directory Tag What it controls
context.ts System Prompt How the system prompt is assembled each turn. Edit this to understand what base instructions Claude receives.
tools.ts Tool Master list of all tools in priority order. Order affects Claude's tool selection probability.
tools/[X]/[X].ts Tool Individual tool description (Claude's vocabulary), permission flags, execution logic.
CLAUDE.md (yours) Memory Persistent context injection. Your primary lever for all persistent behavioral changes.
memdir/ Memory How CLAUDE.md files are discovered and loaded. Edit to understand the search path.
hooks/useCanUseTool.tsx Permission The main permission gate. Reads permissionMode from AppState to decide whether to show a dialog.
utils/permissions/ Permission Bash command allow-list, path-based rules, destructive op detection.
services/compact/ System Prompt Compaction strategies. Controls what gets removed from history and in what order.
commands/ Tool All 80+ slash command implementations. Deterministic code paths that bypass the query loop.
skills/bundled/ Tool 19 bundled skill scripts. Can be overridden with your own versions in ~/.claude/skills/.
tools.ts → feature() calls Feature Flag Compile-time feature gating. Determines which tools even exist in a given build.
services/mcp/client.ts Tool MCP tool discovery and schema generation. Determines how external MCP tools are presented to Claude.
query.ts System Prompt The core agentic loop. Shows exactly how tool calls are dispatched and results returned.
QueryEngine.ts System Prompt API call orchestration, token budget management, compaction trigger points.

Frequently Asked Questions

What is the quickest way to traverse the codebase?
Start at main.tsx for the entry sequence, and then jump to query.ts to see the core agentic loop execution.
How do I track down a specific CLI command?
All commands are registered into the CLI parsing utility inside main.tsx and cleanly map to specific execution modules in the commands/ directory.
Where is the UI defined?
The terminal interface is fully defined using standard React components found within the ink/ and components/ directory abstractions.
How do I test the internal code?
Without the full build environment or Anthropic backend, it is difficult to compile, but you can read through the pure TypeScript logic functionally without issue.
Is the repository documentation up to date?
This leaked code documentation acts as a snapshot analysis of the architecture and might not perfectly align with future official Claude Code iterations.