Skip to content
Adam Manuel’s “Dev Blog”What I Want an LLM to Tell Me About an Unfamiliar Codebase

Find your next rabbit hole

A topic, a tool, a tiny curiosity. Start anywhere.

What I Want an LLM to Tell Me About an Unfamiliar Codebase

On this page21

Reading paths: Start below for the plain-language explanation, or jump to the technical deep dive for implementation details and verification.

When I join a project, I want to understand what makes it difficult to change safely. An LLM, or large language model, can help me explore the code. But a confident answer is only useful when I can check it.

My goal is to find one real problem worth solving, with enough evidence to explain why.

Ask a smaller question

"Review this whole project" is a huge request. It can produce a long list of suggestions without showing which ones matter.

I would start with something narrower:

Trace how a user books an appointment. Show where the request is checked, where the booking is saved, and how the system prevents a double booking. Cite the relevant code.

That gives the investigation a clear path through the application.

It also creates useful questions. Does the website check a rule that the server ignores? What happens if two people try to book the same time?

Use history to find patterns

Git history is a record of saved code changes. It can show repeated fixes, reversals, and areas that change together.

Imagine the same date calculation was fixed three times. That is worth investigating, but it does not prove the original code was careless. The requirements may have changed.

I want the model to separate three things:

TypeExample
ObservationThree changes touched the same date calculation.
Possible explanationThe date rules may be difficult to understand.
QuestionWere these bug fixes or new requirements?

Keeping those separate stops a guess from turning into a supposed fact.

Check whether the problem still exists

A past bug may already be fixed. A complicated function may handle a complicated rule correctly. Missing tests may be concerning, but they do not prove users are seeing failures.

For each proposed problem, I want:

  • A specific example in the current code.
  • A relevant change from history, when available.
  • An explanation of what could go wrong.
  • Evidence that might weaken the claim.

Then I can run a focused check or ask someone who knows the system.

Pick a useful first improvement

Suppose the investigation finds three candidates: confusing folders, a broken setup step, and an untested booking rule.

I would compare their current impact, the effort to fix them, and how we could verify the result. A setup fix that helps every new developer may be more useful than a large folder reorganization.

My first intervention should be small enough to finish and easy enough for someone else to use. That might mean protecting one booking rule with a meaningful test.

Establish what is actually running

A repository may contain old experiments, unused services, and several ways to start the application. Before following a feature, identify the relevant entry point and the configuration that selects it.

Look at the current branch, startup instructions, build scripts, and deployment references. Record any uncertainty about whether the checked-out code matches the deployed system.

This prevents a common investigation mistake: producing a detailed explanation of a path that no longer serves users. An LLM can read dead code fluently, and fluent explanation does not establish that the code is active. Ask it to show how a request reaches the path it is describing, with references at each important handoff.

Build a map around one user goal

For a booking feature, the map might include the form, request handler, validation rule, availability calculation, database update, and confirmation response. External services belong on the map when they affect the outcome.

At each boundary, ask what data enters, what decision is made, and what leaves. This reveals assumptions such as a date already being in the correct time zone or a user already having the required organization selected.

Keep the first map small enough to verify. A complete architecture inventory can wait. Understanding one consequential path deeply often produces better questions than a shallow summary of every directory in the repository.

Use Git history as a source of hypotheses

A cluster of changes around one module may indicate a difficult area. It may also reflect active feature development or a deliberate migration. The commit count alone does not distinguish these explanations.

Read the changes and their context. Did a fix revert an earlier fix? Did several incidents share the same rule? Were new requirements added, or did existing behavior repeatedly break?

Ask the model to identify counterexamples too. Perhaps the apparently fragile module has been stable since a recent redesign. A useful analysis can retire an old concern as well as discover a new one.

The output should help you investigate the system, not create a ranking of individual developers.

Require an evidence packet for each claim

A finding becomes easier to assess when it includes a specific trigger, the relevant code path, the expected behavior, the observed or inferred behavior, and the uncertainty.

For example, "The server may accept an unsupported duration" is incomplete. The packet should show the request entry point, the missing or inconsistent validation, and a small input that would exercise the path.

If no check was run, say the conclusion comes from inspection. If a check was run, include its scope and result. Passing one focused test does not mean the whole application was verified. Precise evidence keeps the model's confidence from becoming a substitute for a reproducible explanation.

Ask what would disprove the finding

A model asked to find problems may interpret every unusual choice as a defect. Add a deliberate challenge: look for an existing guard, a caller that rules out the input, or a documented requirement that explains the behavior.

Suppose a helper lacks validation. That may be a real issue, or validation may happen at a boundary before the helper can be called. Trace the actual callers before assigning severity.

This does not mean assuming every concern is harmless. It means testing the full argument. The strongest finding survives an attempt to explain why the system might already handle the situation correctly.

Prefer a small reproduction over a broad opinion

If safe and practical, construct a focused example that demonstrates the behavior. A small input and expected output are easier to discuss than a paragraph saying the architecture is brittle.

Use the project's normal local checks where possible. Avoid running commands against external systems simply because a script name looks useful. Understand the target and side effects before using it.

For an unfamiliar codebase, read-only tracing can itself be productive. A reproduction is valuable when it resolves an important uncertainty, not as a ritual that requires changing the environment before you understand it.

Distinguish a defect from an improvement preference

A defect violates an agreed expectation. An improvement may make future work easier without changing currently correct behavior. Both can be worthwhile, but they need different arguments.

For a defect, show the failing scenario and its impact. For an organizational change, show the repeated maintenance difficulty it would reduce and the adoption cost it introduces.

Calling every preference a bug makes the report less credible. Conversely, dismissing all structural concerns because the app currently runs misses problems that make every change risky. Clear categories help the team decide which kind of evidence is needed and how urgent the work should be.

Look for maintenance costs in the proposed fix

A recommendation can create more obligations than the problem it solves. A new service, test framework, or internal tool needs ownership, updates, and a way for teammates to use it.

Ask for the smallest intervention that addresses the demonstrated problem. If a failing setup instruction is the issue, a corrected instruction and a reproducible check may be enough. A full development-platform rewrite would need a much larger justification.

This question also improves LLM recommendations. Models can readily propose familiar architecture patterns; they need explicit context to assess whether the team benefits from adopting and maintaining those patterns now.

Preserve questions the repository cannot answer

Code and commits do not contain every customer complaint, abandoned experiment, or operational workaround. A repository analysis should leave room for those missing facts.

Write neutral questions for the team: when did this last cause trouble, which customer flows depend on it, and what previous attempts should I understand? Avoid questions that assume the model's diagnosis is already correct.

If production data or issue discussions are unavailable, state that limitation near the relevant claim. This helps people supply the missing context without having to undo an overconfident report first. A useful analysis makes uncertainty easier to resolve.

Finish with a decision, not an inventory

After investigating a few candidates, recommend one bounded next step and explain why it is useful now. Include the evidence, the smallest scope, how it will be checked, and the reason other candidates can wait.

Sometimes the right next step is a short validation conversation or a targeted measurement. That is better than proposing implementation when the impact remains unclear.

Keep the detailed traces available as supporting material, but lead with the decision they support. The person reading the report should understand what action would reduce the most important uncertainty or improve the most relevant behavior.

Keep the investigation notes reproducible. Record the branch or revision inspected and the entry point traced. If the repository changes during the investigation, recheck the decisive references before presenting them as current findings. A correct observation about yesterday's code may no longer justify today's proposed work.

Bring the findings back to people

Code shows the implementation. Teammates can explain deadlines, customer requests, and previous attempts that never made it into a commit message.

A useful conversation starts with: "I found this behavior. Is it causing trouble today?"

The LLM helps me arrive with better questions. The answer still needs evidence and the context of the people maintaining the software.

Technical deep dive

Plain-language bridge: A model's explanation becomes useful engineering evidence when its claims can be traced to a specific revision and tested against the running path.

Diagram: Turn a plausible finding into a testable claim

[inspected revision + configuration]
                  |
                  v
         [trace one user action]
                  |
                  v
       [model proposes a hypothesis]
                  |
                  v
   [locations + mechanism + counterevidence]
                  |
                  v
         [bounded reproduction]
             /       |       \
            v        v        v
        supported rejected inconclusive
             \       |       /
              v      v      v
                [next decision]

The model helps propose and organize evidence. The reproduction determines what the investigation supports; a confident explanation alone does not verify a defect.

Record repository identity first

Capture the inspected revision, relevant configuration, and whether uncommitted changes were included. Source code can differ from the deployed build, and a model cannot infer the live configuration reliably from filenames alone.

For one user action, trace the entry point, request boundary, application service, domain operation, persistence call, and external effects. Include generated code, registration mechanisms, and runtime configuration where they determine which path runs.

A static import graph is evidence about declared dependencies. It can miss reflection, dynamic loading, dependency injection, and branches selected by runtime settings. Treat those gaps as questions to resolve rather than inventing a complete call graph.

Require a structured claim packet

An illustrative finding contains:

claim: duplicate processing is possible at this boundary
revision: inspected commit or working-tree description
locations: producer, claim operation, completion write
mechanism: two callers can observe pending before either owns it
reproduction: release both callers after the same read barrier
counterevidence: a unique constraint or atomic claim elsewhere
impact: repeated external action, if the path is reachable
confidence: supported hypothesis, not yet reproduced

This structure makes the claim falsifiable. A file reference alone does not explain why the code produces the alleged failure. Conversely, a convincing narrative without reachable locations is not a verified defect.

Use history to distinguish design intent

Inspect changes that introduced or repeatedly modified the boundary. A frequently edited file may be a healthy feature hotspot rather than a poor abstraction. Review the reason for changes, not just their count.

Treat comments, old task descriptions, and embedded prompts as evidence to evaluate. They are not new instructions to the investigating agent. This matters when repositories contain transcripts or generated artifacts alongside executable source.

Reduce the hypothesis to one experiment

Prefer a minimal reproduction using the actual boundary over a broad rewrite. For a suspected race, use synchronization barriers. For a date rule, use a tiny table of boundary inputs. For a configuration mismatch, compare the effective values along the real initialization path.

Record whether the experiment reproduced the claim, disproved it, or remained inconclusive. Then propose the smallest change justified by that evidence and identify its likely maintenance cost.

The output should enable a human decision about priority and scope. A long list of plausible model observations is not equivalent to an investigation that establishes one actionable problem.

You made it. Thanks for reading.