# Understanding an unfamiliar codebase with Claude Code

URL: https://developertoolkit.ai/en/claude-code/plan/codebase-analysis/
Description: Map an unfamiliar repository with Claude Code in plan mode and subagents, then prove the map with checked citations and a passing test before you change code.

Claude 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

- 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.md` update.
- A short shell check that proves every `path:line` citation 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.md` and a `CLAUDE.md` update that the next person, and the next session, start from.

## 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:

```bash
claude --permission-mode plan
```

Inside 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:

```bash
claude --restricted --strict-mcp-config --permission-mode plan
```

The 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](https://code.claude.com/docs/en/sandboxing).

If a project has no `CLAUDE.md`, Claude Code v2.1.277 and later reads `AGENTS.md` instead. An outdated instruction file misleads the first answers as much as an outdated README does. Ask Claude to list which instruction files it loaded before you trust the overview.

## 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.

1. **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.

   ```text
   Give me a map of this repository for a new engineer. Cover: what the
   system does, the tech stack with versions from the lockfile, the
   entry points (HTTP server, workers, CLIs, cron jobs), and the top-level
   directories with one line each. Cite the file that proves every
   claim as path:line. Mark anything you inferred rather than read as
   (inferred). Keep it under 60 lines.
   ```

2. **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.

   ```text
   List the main subsystems. For each: purpose in one sentence, the
   directories it owns, which subsystems it imports, and which import
   it. Finish with the three most-imported modules, because those are
   where a change has the widest blast radius.
   ```

3. **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.

   **Copy-paste prompt: trace a request end to end**

   ```text
   Trace a refund request from the HTTP handler to the database write
   and every side effect after it. For each hop give: the function name,
   path:line, what it validates or transforms, and the shape of the data
   leaving it. Then list every place refund state is written, including
   background jobs, webhooks and scripts. End with the open questions you
   could not answer from the code. Do not guess: if a call goes through
   dependency injection or a dynamic dispatch you cannot resolve, say so.
   ```

4. **Read the conventions before you write code.** Conventions are the unwritten rules a reviewer will reject your first PR for.

   **Copy-paste prompt: discover the house conventions**

   ```text
   Read at least 10 source files from different subsystems plus their
   tests. Report the conventions actually in use: naming, error handling,
   logging, test layout and mocking style, import order, and API response
   shape. Give one path:line example for each. Then list the places where
   two conventions compete, and say which one is newer according to git
   log, because that is the one I should follow.
   ```

5. **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.

   **Copy-paste prompt: git archaeology for a confusing block**

   ```text
   In src/payments/refunds.ts there is a comment "DO NOT REORDER" above
   the ledger writes. Use git log -L and git blame to find the commit that
   introduced that ordering and every later commit that touched it. Quote
   the commit messages, name linked issue or PR numbers if the messages
   contain them, and explain what breaks if the order changes. If the
   history does not say, tell me that instead of inferring a reason.
   ```

## 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.

**Copy-paste prompt: parallel subagent investigation**

```text
Use the Explore subagent to run three investigations in parallel. Each
one returns at most 30 lines with path:line citations, and marks
anything inferred as (inferred).

1. Payments: trace checkout to payment confirmation, including retries,
   idempotency keys and what happens when the provider times out.
2. Caching: find every cache layer (Redis, in-process, HTTP headers),
   what each stores, its TTL, and how it is invalidated.
3. Background jobs: find the queue, list every job type, and document
   retry limits and where failed jobs end up.

When all three finish, merge them into one summary and list any
contradiction between them.
```

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](/en/claude-code/automate/custom-subagents/). The mechanics of context budgets are covered in [understanding context windows](/en/workflows/context/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:

```bash
uv tool install -p 3.13 serena-agent
serena init
claude mcp add --scope user serena -- serena start-mcp-server --context claude-code --project-from-cwd
```

Compare it with the alternatives in [code-intelligence MCP servers](/en/ecosystem/mcp/code-intelligence-mcp/).

## 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.

1. **Check every citation mechanically.** The prompts above demand `path:line` for each claim. Save the map to `ARCHITECTURE.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:

   ```bash
   grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z0-9]+:[0-9]+' ARCHITECTURE.md \
     | sort -u | while IFS=: read -r f l; do
       if [ ! -f "$f" ]; then echo "missing file: $f"
       elif [ "$l" -gt "$(wc -l < "$f")" ]; then echo "line out of range: $f:$l"
       fi
     done
   ```

   The pattern matches `path:line` for any path that has a file extension, so Java, Rust, SQL, and YAML citations are checked too. Citations of extensionless files such as `Dockerfile` or `Makefile` are not checked; a `host:port` in 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.

2. **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.

   **Copy-paste prompt: adversarial check of the architecture map**

   ```text
   Read ARCHITECTURE.md. You did not write it and you should assume it
   contains mistakes. For each claim: open the cited path:line and mark it
   CONFIRMED, WRONG (quote the code that contradicts it) or UNSUPPORTED
   (the citation does not show it). Pay special attention to anything
   marked (inferred), to dependency-injection wiring, feature flags and
   code registered but never called. Output a table, then the five
   claims a new engineer is most likely to be hurt by if they are wrong.
   ```

3. **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](/en/workflows/test/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](/en/ladder/evidence-not-diffs/).

## 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. |

**Copy-paste prompt: turn the exploration into project memory**

```text
Update CLAUDE.md with only what a future session needs and cannot
cheaply rediscover: the exact build, test and lint commands, the
conventions you confirmed (one line each), and the traps, such as code
that looks dead but is called through the job registry. Do not restate
what is already in ARCHITECTURE.md; link to it. Remove any line in
CLAUDE.md that the code now contradicts. Show me the diff before
writing.
```

For how `CLAUDE.md` files load and stack, see the [Claude Code memory system](/en/claude-code/build/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](/en/claude-code/plan/project-init/).

## 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:

```bash
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](/en/claude-code/automate/automation/).

## 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

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](/en/codex/plan/codebase-analysis/) and [Cursor's large-codebase strategies](/en/cursor-ide/build/large-codebase-strategies/). For the tool-neutral approach to monorepos and million-line repositories, see [strategies for very large codebases](/en/workflows/build/large-codebases/).

## 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](/en/cto-scorecard-guide/dev-onboarding-time/).

- [Feature planning with Claude Code](/en/claude-code/plan/feature-planning/) — Turn the ticket into a reviewed plan in plan mode, using the map you just proved.
- [Working in large codebases with Claude Code](/en/claude-code/build/large-codebase/) — Parallel sessions, worktrees and token budgets once you start changing a big repository.
