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:
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.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.normalizeMessagesForAPI() function strips all UI-only message types before sending. What's left is exactly what Claude reasons over.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?"
checkPermissions(). BashTool does not declare isReadOnly: () => true, so buildTool()'s fail-closed defaults kick in. Every bash invocation routes to the permission system.permissionMode from AppState. In default interactive mode it calls showPermissionDialog() for non-read-only tools.allow-list for commands that auto-approve (like ls, cat, git status) vs those that always ask.--auto flag, settings.json permissionMode
--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?"
CLAUDE.md files starting at the current directory and walking up to the root. It also checks ~/.claude/CLAUDE.md for global preferences.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.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?"
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.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. |
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)
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.
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 |
|---|---|---|
GrepTool — isReadOnly: true |
Any | ✅ Auto-approved, runs silently |
GlobTool — isReadOnly: 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) |
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.
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
| Technique | How 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.
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.ts → commands/[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:
| Command | What it does (from source) |
|---|---|
/commit | Runs git diff, analyzes changes, generates a conventional commit message, shows preview, lets you confirm. Includes co-author attribution by default. |
/review | Performs a structured code review of staged or specified changes: correctness, security, performance, style. |
/compact | Manually triggers context compaction with a user-readable summary of what was removed. |
/memory | Opens the CLAUDE.md file for your current project in an editor — the fastest way to add persistent context. |
/mcp | Manage MCP server connections: list, add, remove, auth. |
/session | Session management: list recent sessions, resume a previous one, view transcripts. |
/new | Starts a fresh session — clears message history but preserves CLAUDE.md context. |
/autofix-pr | Fetches a PR's CI failures and automatically applies fixes. A power-user command that chains multiple tools. |
/bughunter | Initiates 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.
| Skill | What it does and when to use it |
|---|---|
/simplify | Reviews recently changed code and suggests refactoring for clarity. Run after you've gotten something working to clean it up. |
/loop | Runs a prompt or command on a recurring basis within a session. Useful for "keep watching this file and tell me when X changes". |
/schedule | Creates a cron-like scheduled agent trigger. The agent will run at the specified interval, independent of your active session. |
/update-config | Modifies settings.json with proper hook support — safer than editing the file directly. |
/claude-api | Scaffolds a new project that uses the Anthropic SDK. A guided setup flow for building Claude-powered apps. |
/review-pr | AI-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:
| Tool | Search hint phrase | How 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.
utils/permissions/
--auto flag or add path to auto-approve list in settingstools.ts → tool ordering
getAllBaseTools() return list and not deferred.isConcurrencySafe: false (the default) run serially. If a custom tool is running in parallel unexpectedly, it was explicitly marked safe. Check the tool definition./commit skill script.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:
- Read the
descriptionstring 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. - Check
isReadOnlyandisConcurrencySafe. 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. - Read
checkPermissions()if it exists. Any custom permission logic is here. For BashTool, this is where the command classifier runs. - 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." - Check
renderToolUseMessage()andrenderToolResultMessage(). These are the terminal UI renderers. They don't affect Claude's reasoning but tell you what the user sees in the REPL. - Look for a
shouldDeferproperty. 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 |
The main permission gate. Reads permissionMode from AppState to decide whether to show a dialog. | |
utils/permissions/ |
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?
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?
main.tsx and cleanly map to specific execution modules in the commands/ directory.Where is the UI defined?
ink/ and components/ directory abstractions.