You Just Took the Badge: Reading a Codebase Like Lucas Hood
The job nobody handed him a file for
In the opening of Banshee, a man with no real name walks into a small Pennsylvania town wearing a badge that used to belong to a sheriff who’s now dead — a man he impersonated on a whim, in a parking lot, minutes after the real Lucas Hood was shot. By the next morning he’s expected to run the department. Know the deputies. Know the town’s politics. Know who’s actually dangerous and who just acts like it. Nobody hands him a case file. Nobody sits him down for a week of onboarding. He has exactly the knowledge he can gather himself, as fast as he can gather it, and the job doesn’t pause to wait for him to catch up.
That is precisely what it feels like to be handed a ten-year-old codebase on your first week. Nobody wrote down why the billing service retries three times instead of five. Nobody remembers why there are two different “user” tables. The people who made those decisions have mostly left, the same way the real Lucas Hood is in the ground before episode one even ends. You inherited the badge. You did not inherit the understanding that’s supposed to come with it.
Diagrams and ELI5 explainers don’t fix this, for the same reason a briefing from the mayor wouldn’t actually prepare Hood for Banshee. A summary hands you someone else’s conclusions. It doesn’t hand you the dozens of small judgment calls that produced those conclusions — and those judgment calls are the only thing that makes a town, or a codebase, make sense. You have to go rebuild that understanding yourself, on the ground, the way Hood does.
Two habits get you there. Everything else in this guide — every tool, every command — exists to support one of these two.
Habit 1 — Find out who actually runs the town
Banshee has a mayor. The mayor holds meetings, signs things, gives the town its official org chart. The mayor is not, in any way that matters, in charge. The actual power in Banshee runs through Kai Proctor — an Amish-raised crime boss who launders his operation through a legitimate meatpacking business, and who every real decision in town eventually routes through whether the org chart admits it or not.
Fred Brooks said the version of this that matters for code: show me your tables and I won’t need your flowcharts. Before you read a single function, find the actual nouns the system runs on — the schema, the core types, the messages passed between services — and ask, for each one: what states can this live in, who’s allowed to create it, who mutates it, who reads it. A huge codebase, like a small town, usually has only 5–10 real nouns underneath all the apparent complexity. Once you know them and their lifecycle, most files stop being mysterious. A given file is just “the thing that moves an Order from pending to fulfilled,” the same way a given scene in Banshee is just “Proctor protecting a shipment” no matter which deputy happens to be standing in the frame.
The mistake people make is reading the org chart — the architecture doc, the service-ownership wiki page — and believing it. Read the data instead. The data doesn’t lie about who’s actually in charge of a given piece of behavior; the documentation sometimes does, usually by accident, because nobody updated it after the real Kai Proctor of the system took over.
Habit 2 — Walk one real case to the end, and call your shots before you see them
Hood doesn’t read personnel files on day one. He takes a real call — an actual disturbance, an actual body — and walks in with no script, because the town does not wait for him to be ready. What he does instead, every single time, is form a read on the situation before he acts on it — who’s armed, who’s lying, who’s about to run — and then find out, immediately, whether he was right. Wrong reads get corrected fast and violently. Right reads get banked as something he now actually knows about this town, not something he was told about it.
Do the same with a codebase. Don’t try to “understand the codebase” — nobody holds the whole thing in their head, including the people who built it. Pick one real case: a single API request, a single button click. Follow it through every layer with a debugger or log statements until it hits storage and comes back. Write down the actual chain of files and functions as a plain, ugly list. Your own list, built from one real case, is worth more than any architecture diagram someone handed you.
The part that makes this actually work: before you open the next function, say what you think it does and what it’ll return — then look. Every wrong guess is Hood getting a read wrong and finding out in real time; it shows you exactly where your model of the system breaks down, and that break is where the real learning happens. Reading without predicting first is how you end up thinking you understand a town you’ve only ever been told about.
Then test it like Hood tests whether he’s actually earned the badge: make a small, deliberate change — add a field, alter one behavior — and see whether the blast radius matches what you predicted. If you can call your shot and watch it land, you understand the system. If you can’t, you’re still reciting someone else’s case file.
A worked case: tracing one request like Hood works one call
Say the bug report is “sometimes the order confirmation email never arrives.” Don’t start by reading the email service’s source top to bottom. Walk the actual case:
- Reproduce the call. Place a real order in a dev environment and watch it happen, the way Hood would walk the actual street instead of reading a report about it.
- Guess the first hop, then check. You’d expect the checkout endpoint to publish an
OrderPlacedevent. Grep for who’s actually listening —rg "OrderPlaced"— before you assume anything about the architecture diagram’s accuracy. - Guess the second hop, then check. You’d expect exactly one consumer to pick that event up and queue the email job. If
rgturns up two consumers instead of one, you just found something the diagram never told you — maybe a legacy consumer nobody decommissioned, which is exactly the kind of fact Hood only gets by being on the street, not by reading the department roster. - Guess the failure point, then check with a debugger. Breakpoint inside the email job handler. Predict whether it throws, silently swallows an error, or succeeds and the email provider just drops it. The live call stack tells you which, immediately — no more guessing.
- Write the chain down.
checkout.controller → events.publish('OrderPlaced') → [legacy.emailWorker, new.notificationWorker] → emailWorker swallows a rejected-address error silently. That ugly five-line list is now worth more to you than the entire onboarding doc, because you built it by walking the actual street instead of reading about it.
That’s the whole method. Everything from here is just better equipment for doing exactly this, faster.
The toolkit — Job’s rig, Brock’s rulebook, and Sugar’s bar
Hood doesn’t work this alone. Job gets him the digital trail. Brock, the deputy who actually knows departmental procedure, gets him the official relationships. Sugar, who’s owned the bar long enough to remember everyone’s history, gets him the stories nobody put in writing. Use the same three kinds of help.
Job’s rig — search and navigation, for moving fast
ripgrep (rg) — find every place a string, error message, or config key is actually used, instantly.
# find every place a config key is read
rg "MAX_RETRIES" --type ts
# find the exact log line you saw at 2am, and land straight on the source
rg "connection pool exhausted"
# case-insensitive, only inside src/, with 2 lines of context
rg -i "retry" src/ -C 2
Why it helps: a runtime error message is a faster way into the code than reasoning your way there from the architecture — the same way a license plate is a faster lead than a theory.
ast-grep (sg) — search, and even rewrite, by code structure instead of text.
# every call to save() with exactly two arguments, across the whole repo
sg -p 'save($A, $B)' --lang ts
# rewrite every such call to pass an explicit options object
sg -p 'save($A, $B)' -r 'save($A, { value: $B })' --lang ts
Why it helps: text search can’t express “every place this pattern occurs with this shape.” Once naming drifts across a decade of contributors, structural search is the only thing that still finds every instance.
Your editor’s language server. “Go to definition,” “Find all references,” and especially call hierarchy — who calls this, and who calls them. In VS Code: right-click a symbol → “Show Call Hierarchy.” Why it helps: this is Brock’s rulebook — the official, on-paper relationships. It builds your call chain for you, layer by layer, faster than reading top-down ever will.
Sourcegraph / GitHub code search — the same idea as ripgrep, scaled to a system split across many repos. Why it helps: useful the moment the case you’re working crosses jurisdiction lines — into a library, a sibling service, a repo you don’t normally touch.
Walking the beat — tracing what actually runs
A real debugger — VS Code’s debugger, LLDB/GDB, whatever your language uses. Breakpoint at the entry point, step through, read the stack.
gdb ./myapp
(gdb) break handle_request
(gdb) run
(gdb) bt # backtrace = your call chain, for free
Why it helps: the stack trace is the chain of custody you’d otherwise hand-assemble from scattered prints.
rr (Linux; C, C++, Rust, Go) — record a run, then step backwards through it.
rr record ./myapp
rr replay
(rr) reverse-continue # rewind to the exact moment a bad value was set
Why it helps: when a value is wrong three calls deep, you rewind straight to where it went wrong instead of re-running the whole case from the top on a new guess.
Flame graphs (perf, py-spy, samply, async-profiler) — show which code paths actually execute, and how often.
# Python: sample a running process without restarting it
py-spy record -o profile.svg --pid 12345
# Linux native (C/C++/Rust etc.)
perf record -g -- ./myapp
perf report
Why it helps: finds the real, load-bearing path through a codebase that’s full of code sitting right next to it that nobody actually calls anymore — the department’s dead files, still in the cabinet.
OpenTelemetry + Jaeger — watch one request hop across services, queues, and databases as a single timeline. Why it helps: essential the moment “trace the request” means crossing process boundaries a debugger physically cannot follow — the case now spans county lines.
The county records office — learning the nouns
DBeaver, or the database’s own CLI — read the schema before the code.
-- Postgres: full description of a table, including constraints, indexes, and foreign keys
\d+ orders
Read the output like a deed: what columns can be NULL (optional state), which columns reference other tables (relationships — who this noun depends on), and which indexes exist (which lookups the system was actually built to do quickly — a strong hint about its real, load-bearing access patterns).
Why it helps: the schema tells you every state a thing can legally be in; the code only tells you how it gets there.
SchemaSpy — generates a browsable relationship diagram straight from a live database.
java -jar schemaspy.jar -t pgsql -db mydb -u user -p pass -o ./schema-docs
Why it helps: gives you the full map of nouns and their relationships without hand-tracing every foreign key yourself.
Generated API docs (cargo doc, Javadoc, TypeDoc, Sphinx) — every core type, browsable in one place.
cargo doc --open
Why it helps: a fast index of every noun in the system, without opening dozens of files to assemble it yourself.
Sugar’s bar — the history nobody wrote down
git log -L — the complete history of a single function, one commit at a time.
git log -L :handleRequest:src/server.ts
Why it helps: hugely underused, and the single best source of why. You see the exact commit that added the weird retry logic, and usually its message tells you the incident that caused it — the story Sugar remembers that never made it into any doc.
git blame -w -C — points at the real author of a line, not whoever last reformatted it.
git blame -w -C -- src/server.ts
Why it helps: -w ignores whitespace-only changes, -C follows code that was moved between files — without both flags, blame constantly points you at “the person who ran prettier,” not the person who actually wrote the logic.
GitLens (VS Code) or tig (terminal) — make browsing history fast enough that you’ll actually reach for it day to day, the way you’d actually walk into the bar instead of only going once a season.
git log --reverse on the core directory — read the project from its earliest, smallest commits forward.
git log --reverse --oneline -- src/core | head -30
Why it helps: the earliest commits show you the original design, before a decade of edge cases and incident-driven patches piled on top of it. You get the skeleton of the town before you get all the grudges that got layered onto it later.
The corkboard — building your own model
Plain Markdown or Obsidian — write down call chains and data lifecycles as you trace them. Linking notes ([[Order]] → [[FulfillmentWorker]]) grows into your own map of the system over time, the same way a detective’s corkboard accumulates into something only they fully understand, built pin by pin from cases they actually worked.
Excalidraw — sketch a diagram after you’ve traced something, from what you actually found. A diagram built from your own case is worth ten someone handed you on day one.
The informant — AI assistants, used carefully
Claude Code, or any repo-aware agent, is excellent for “where does X get validated?” or “trace this request for me.” It’s an informant: fast, useful, and not something you act on without verifying yourself. The way to use it without losing the skill — write your own prediction first, then ask, then verify by jumping to the actual code it points at. Used that way, it shortens the loop between your guess and the answer. It never replaces making the guess, the same way Hood still has to walk in and confirm whatever Job’s rig turned up before he trusts it with his life.
If you only adopt three
ripgrep, a real debugger with breakpoints, and git log -L. Together they cover finding the code, seeing what it actually does, and learning why it got that way — which is most of what reading a large codebase actually requires.
Earning the badge
Reciting Banshee’s history doesn’t make you its sheriff. Walking into a call nobody warned you about, and handling it the way the town’s real history would predict, does. The same test applies to a codebase: pick one real feature — how a single event moves from the place it’s created, through whatever it passes through, into storage, and back out through a query — trace it end to end with the tools above, and write down the chain and the data shape at every step. You’ll understand that path better than any architecture doc could ever teach you, and by the second or third case you walk this way, you’ll stop feeling like you’re wearing someone else’s badge.
Was this article helpful?
Your feedback helps us create better content.
Discussion.
Handpicked for you.
"Continuity is the secret to mastery. If you enjoyed our deep dive into You Just Took the Badge: Reading a Codebase Like Lucas Hood, we've selected these related insights based on your unique interests."