Understanding an unfamiliar codebase with Claude Code
PLClaude Code maps an unfamiliar codebase from the terminal in a read-only plan-mode session: a top-down overview, a traced path through the one flow a change touches, and parallel subagents that keep file contents out of the main context. The map is trustworthy only after its file-and-line citations pass a script and its central claim passes a test.
You joined the team on Monday. The repository has seven years of history, the architect who designed the order pipeline left last year, and the README describes a queue the code stopped using. Your first ticket touches refunds, and nobody can tell you where refund state actually lives. This page is for that developer, and for the tech lead who wants every new hire to reach a first safe change without a week of shadowing.
What you get from mapping a codebase with Claude Code
Section titled “What you get from mapping a codebase with Claude Code”- A read-only session setup that cannot edit files while you explore, with a stricter variant for code you do not trust.
- Six copy-paste prompts: a flow trace, convention discovery, git archaeology, a parallel subagent fan-out, an adversarial check of the map, and a
CLAUDE.mdupdate. - A short shell check that proves every
path:linecitation in the map exists, so you verify the map without rereading the code. - A characterization test that turns “this is how refunds work” into a claim CI re-checks on every commit.
- An
ARCHITECTURE.mdand aCLAUDE.mdupdate that the next person, and the next session, start from.
Start a read-only exploration session
Section titled “Start a read-only exploration session”Since Claude Code v2.1.283 (both release channels on 2026-10-12), interactive sessions start in auto mode, where a classifier approves actions, including edits. The exceptions are sessions where a settings file sets disableAutoMode and sessions on a model auto mode does not support, which start in Manual. Exploration needs the opposite, so choose plan mode on purpose. In plan mode Claude reads files and, where auto mode is available, runs the commands its classifier approves. It cannot edit anything until you approve a plan.
Run this in a terminal at the repository root:
claude --permission-mode planInside a session that is already running, press Shift+Tab until the mode indicator shows plan mode, or type /plan.
Plan mode is a workflow guardrail, not an isolation boundary. For a repository you do not trust, such as a vendor drop or an acquired codebase, add --restricted. In Claude Code 2.1.283 that flag removes Bash and the other code-running tools and WebFetch, ignores user, project, and local settings files, and confines file tools to the working directory. MCP servers still load unless you add --strict-mcp-config (checked in claude --help 2.1.283), and a project .mcp.json in an untrusted repository can start one, so add both:
claude --restricted --strict-mcp-config --permission-mode planThe repository’s CLAUDE.md or AGENTS.md is still read as instructions; treat it as untrusted input, or add --safe-mode, which per claude --help 2.1.283 disables CLAUDE.md, skills, hooks, MCP servers and custom agents.
The trade-off: without Bash, Claude cannot run git log or git blame, so do the git-history step later in a normal plan-mode session once you have read what the build scripts and hooks do. For stronger isolation, run the session inside a container or with sandboxed Bash.
Map the codebase from the top down
Section titled “Map the codebase from the top down”The order matters. Each step narrows the question, so Claude reads fewer files at every step and the answers get more specific.
-
Get the overview. Claude reads the manifest files, the directory tree, configuration, and entry points, then returns a short summary. That summary is your index, not your understanding.
Give me a map of this repository for a new engineer. Cover: what thesystem does, the tech stack with versions from the lockfile, theentry points (HTTP server, workers, CLIs, cron jobs), and the top-leveldirectories with one line each. Cite the file that proves everyclaim as path:line. Mark anything you inferred rather than read as(inferred). Keep it under 60 lines. -
Name the subsystems and their dependencies. Ask for modules and the direction of dependencies. The direction tells you what you can change safely: a module that nothing imports is cheap to touch.
List the main subsystems. For each: purpose in one sentence, thedirectories it owns, which subsystems it imports, and which importit. Finish with the three most-imported modules, because those arewhere a change has the widest blast radius. -
Trace the one flow you must change. Scope it to your ticket. A trace of one path through the code is worth more than a tour of every module.
-
Read the conventions before you write code. Conventions are the unwritten rules a reviewer will reject your first PR for.
-
Ask git why the strange code exists. Commit messages and blame explain decisions the code alone cannot, such as a retry loop that looks redundant or a “do not change” comment.
Keep the main context clean with subagents
Section titled “Keep the main context clean with subagents”Every file Claude reads in the main session stays in its context window. A broad investigation can crowd out the conversation you actually need, and answer quality drops as the window fills. Subagents solve this: each one explores in its own context and returns only a summary. Claude Code ships built-in Explore, Plan, and general-purpose subagents, and subagents run in the background by default, so you keep working while they read.
Use /context to see how full the main window is. Run /compact when a long exploration has served its purpose, or /clear when you switch to an unrelated area. If you define the same investigation every week, save it as a custom subagent in .claude/agents/; see custom subagents. The mechanics of context budgets are covered in understanding context windows.
On a very large repository, text search and file reads are the most expensive part of exploration. A code-intelligence MCP server answers “who calls this function” through a language server instead. Serena, a community server from Oraios, is one option (PyPI serena-agent 1.7.0, checked 2026-09-26). Its maintainers warn against installing it from an MCP or plugin marketplace, because those listings carry outdated commands, so install it from upstream:
uv tool install -p 3.13 serena-agentserena initclaude mcp add --scope user serena -- serena start-mcp-server --context claude-code --project-from-cwdCompare it with the alternatives in code-intelligence MCP servers.
Prove the map before you trust it
Section titled “Prove the map before you trust it”A fluent summary is not a correct one. The agent can cite a file that was renamed, describe a middleware that is registered but disabled, or smooth over a dynamic dispatch it never resolved. You do not fix that by reading every file yourself. You fix it by making each claim checkable and letting machines check it. Run these three checks in order, from cheapest to strongest.
-
Check every citation mechanically. The prompts above demand
path:linefor each claim. Save the map toARCHITECTURE.md, then run this in a terminal at the repository root. It prints every citation whose file is missing or whose line number is past the end of the file:Terminal window grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z0-9]+:[0-9]+' ARCHITECTURE.md \| sort -u | while IFS=: read -r f l; doif [ ! -f "$f" ]; then echo "missing file: $f"elif [ "$l" -gt "$(wc -l < "$f")" ]; then echo "line out of range: $f:$l"fidoneThe pattern matches
path:linefor any path that has a file extension, so Java, Rust, SQL, and YAML citations are checked too. Citations of extensionless files such asDockerfileorMakefileare not checked; ahost:portin the map shows up as a missing file you can ignore. An empty output means every citation points at real code. Claims with no citation are not checked at all, which is one more reason the prompts demand one per claim. The check does not prove the claims are right, only that they are anchored to something you can open. -
Attack the map with a fresh context. A second pass that did not write the map has no stake in defending it. Start a new session with
/clear, or delegate the review to a subagent, so the reviewer does not inherit the first pass’s assumptions. -
Turn the central claim into a test. Pick the one claim your ticket depends on, for example “a refund above the captured amount is rejected before any ledger write”. Leave plan mode, ask Claude to write a characterization test that asserts exactly that current behaviour, and run it. A passing test is evidence that the map matches the code. A failing test is the most valuable finding of the week, and CI re-checks the claim on every commit after it merges. The technique is covered in depth in characterization tests.
Who signs off. The developer signs off the map for the area they are about to change, after all three checks pass. When ARCHITECTURE.md or CLAUDE.md changes, it goes through a normal pull request, and the tech lead or the code owner of that area reviews the diff to the document, which is a few dozen lines, not the code behind it. That is the shift from reading code to reviewing evidence described in evidence, not diffs.
Save what you learned for the next session
Section titled “Save what you learned for the next session”Exploration that is not written down gets repeated by the next person and by your own next session. Keep two files, with different readers.
| File | Reader | What goes in it |
|---|---|---|
ARCHITECTURE.md | Humans | The checked map: subsystems, the flows you traced, dependency direction, open questions. |
CLAUDE.md (or AGENTS.md) | Every future agent session | Build and test commands, conventions, and the traps, in as few lines as possible. |
For how CLAUDE.md files load and stack, see the Claude Code memory system. If the repository has no instruction file at all, run /init first, as described in running /init on a codebase you just joined.
Run a recurring headless codebase report
Section titled “Run a recurring headless codebase report”For a report that a tech lead wants every sprint, such as untested modules or TODO hot spots, run Claude Code headless. claude -p sessions start in Manual mode (auto mode on third-party providers or with telemetry off, from v2.1.285); in Manual, reading files needs no approval. --permission-mode dontAsk denies anything that would prompt on every provider, so the run cannot edit files, --permission-prompts none makes sure nothing waits for an answer, and --allowedTools lets through the one command the report needs:
claude -p "List source files under src/ that have no test file importing them. \Group by top-level directory and give each file's last commit date from git log. \Return only JSON: {\"untested\": [{\"path\": string, \"lastCommit\": string}]}" \ --permission-mode dontAsk \ --permission-prompts none \ --allowedTools "Bash(git log *)" \ --max-budget-usd 5 \ --output-format json | jq -r '.result' > untested-report.json--output-format json wraps the answer in a result envelope, so jq -r '.result' extracts the JSON the prompt asked for. Commit the previous report or keep it as a CI artifact, and let a script compare the two, so a human only looks at what changed. --max-budget-usd caps spend and works only with --print (-p); all four flags are from claude --help 2.1.283. Wiring this into CI is covered in Claude Code automation.
Where codebase exploration goes wrong, and how to recover
Section titled “Where codebase exploration goes wrong, and how to recover”| Symptom | Likely cause | Recovery |
|---|---|---|
| The overview is generic (“uses a layered architecture”) | The prompt asked for a summary, not citations | Re-ask with “cite path:line for every claim, mark inferences”. Generic prose with no citations is a guess. |
| Citations fail the shell check | Files were renamed since a doc Claude read, or the model reconstructed a path | Tell Claude which citations failed and ask it to re-open those files. Never repair a citation by hand without re-reading the code. |
| Answers get vaguer as the session goes on | The context window is full of file contents | Check /context. Move broad reading to subagents, then /compact, or /clear and continue from ARCHITECTURE.md. |
| Claude describes the system in the README, not the one in the code | Stale documentation was treated as ground truth | Say “when documentation and code disagree, the code wins; list every disagreement you find”. Those disagreements are useful output. |
| A trace stops at dependency injection or a plugin registry | Static reading cannot resolve runtime wiring | Ask for the registration site, then confirm with a characterization test or a debug log in a local run, not a longer prompt. |
| Claude started editing during exploration | The session was in auto mode, the starting mode from v2.1.283 | Press Esc, run /rewind to restore the files, switch to plan mode, and continue. |
| The unfamiliar architecture is misread as standard MVC | Unconventional naming (a handlers/ folder that holds domain logic) | State the correction once, and add it to CLAUDE.md so the next session does not repeat the mistake. |
How Codex and Cursor handle the same job
Section titled “How Codex and Cursor handle the same job”This page covers Claude Code only. The workflow of map, trace, prove, and save carries over to other tools, but the controls differ: Codex has its own permission profiles and /plan, and Cursor has Plan Mode in the editor. See understanding codebases with Codex and Cursor’s large-codebase strategies. For the tool-neutral approach to monorepos and million-line repositories, see strategies for very large codebases.
Where to go next after mapping the codebase
Section titled “Where to go next after mapping the codebase”Once the map passes its checks, plan the change against it. Tech leads measuring how fast new engineers reach a first safe change can use the developer onboarding metric.