The first task in an unfamiliar repository can feel like walking into a city without street signs. There are hundreds of files, several ways to run the application, and a ticket that says something deceptively small: “Fix the checkout error.”
The tempting move is to start opening files until the code looks familiar. Resist it. Your first job is not to understand everything. It is to build a useful enough map to make one safe change.
This guide gives you a repeatable first-hour workflow. The time estimates are only a guide; large monorepos and unfamiliar stacks take longer.
The rule: follow the task, not the file tree
A repository is a record of decisions. Its README tells you what maintainers think matters. Its scripts reveal the intended workflow. Tests reveal expected behaviour. Git history shows what changed and sometimes why. The code itself connects those clues.
Use these sources in roughly this order:
- The task: what behaviour is wrong, and what should happen instead?
- The instructions: README,
CONTRIBUTING.md,AGENTS.md, package scripts, Makefile, or equivalent. - The shape: top-level directories, entry points, app boundaries, and tests.
- One path through the system: follow the relevant behaviour end to end.
- The evidence: tests, logs, recent commits, and a small local reproduction.
You are trying to answer a handful of questions, not memorize the repository.
A practical first-hour checklist
1. Establish your bearings
Before changing anything, run:
git status --short
git branch --show-current
git log -5 --oneline
This tells you whether the working tree already has changes, which branch you are on, and what has recently moved. Do not discard existing edits just to make the tree look clean. Find out whether they belong to you, a teammate, or the task.
Then inspect the top level:
ls -la
Look for the README, dependency manifests, lockfiles, test configuration, CI workflows, Docker files, and environment examples. If you see apps/ and packages/, you may be in a monorepo. If you see src/, tests/, and one package manifest, it may be a smaller application. Treat these as clues, not guarantees.
2. Find the intended way to run it
Read the README and project-specific instructions. Then inspect scripts in the package manifest, Makefile, or task runner. Prefer the repository’s documented commands to inventing your own.
For a Node project, you might see:
{
"scripts": {
"dev": "vite",
"test": "vitest run",
"lint": "eslint .",
"build": "tsc -b && vite build"
}
}
The example is illustrative; use the commands in the repository you actually have. Check the required runtime version and environment variables. Never paste secrets into a chat, ticket, screenshot, or log.
Run the smallest safe health check first. If tests are slow, start with the narrow suite relevant to your task. Record the baseline: a failing test that existed before your change is different from one your change introduced.
3. Trace one real flow
Suppose the ticket concerns duplicate checkout orders. Do not read the entire src/ folder. Search for the endpoint, handler, UI action, or test that mentions checkout.
Trace this path:
User clicks Buy
↓
Frontend submits request
↓
API route validates input
↓
Order service applies business rules
↓
Database writes order
↓
Response updates the UI
The actual path may differ. Your job is to replace this sketch with evidence from the code.
At each boundary, ask:
- What data enters and leaves this layer?
- Where is validation performed?
- What can fail here?
- Which function owns the business rule?
- What test demonstrates the intended behaviour?
Use IDE references, symbol search, grep, or language-server navigation. Read the callers and tests around a relevant function before editing it. A function named createOrder might only coordinate work; the actual invariant could live in a service or database constraint.
4. Let tests and history tell you what the code means
Tests are executable examples of expected behaviour. Find the nearest relevant test and read its setup, inputs, assertions, and teardown. If a test is missing, that is information—not permission to guess.
Git history can help explain a surprising design:
git log --oneline -- path/to/file
git blame path/to/file
History is evidence, not truth. A previous implementation may be outdated, and a commit message may not explain every trade-off. Use history to form questions, then confirm them against current behaviour and documentation.
5. Write a tiny map before touching code
Keep a note like this in your scratchpad:
Entry point: POST /api/orders
Business rule: src/services/order-service.ts
Persistence: src/db/orders.ts
Relevant tests: tests/orders.test.ts
Observed symptom: duplicate POST can create two records
Unknown: is idempotency intended at API or service boundary?
Next check: reproduce with two equivalent requests
Notice the “Unknown” line. Strong engineers do not pretend every assumption is a fact. They make uncertainty visible and choose the next experiment that reduces it.
Using an AI coding assistant without losing the plot
AI can accelerate repository exploration, but asking “Explain this whole codebase” usually produces a broad, low-signal summary. Give it a bounded question and inspect the files it cites.
A better prompt:
I need to investigate duplicate orders during checkout. Do not edit files yet. Trace the request path from the route to persistence. List the exact files and functions involved, explain what evidence supports each claim, and identify what is still unknown. Then suggest the smallest reproduction test.
Verify the result yourself. Paths, function names, and architectural summaries can be wrong or stale. The model’s confident tone is not evidence.
Common traps
- Reading everything first. You spend an hour on unrelated code and still do not know where the behaviour begins.
- Changing the first plausible file. The visible symptom may be downstream of the real cause.
- Skipping the baseline. You cannot reliably attribute failures to your change.
- Trusting a generated overview. Summaries compress uncertainty and may invent relationships.
- Treating old comments as contracts. Verify important behaviour against tests and current execution.
Your exit criteria
Before making the first edit, you should be able to explain—in plain language—the relevant user behaviour, the path through the system, the likely owner of the rule, how to reproduce the issue, and how you will verify a fix.
You do not need to understand every folder, every abstraction, or the original author’s entire philosophy.
The goal is not total understanding. It is enough verified understanding to make the next change safely.
Try it yourself
Pick a public repository you have never used. Set a 20-minute timer. Find its run command, test command, main entry point, one feature path, and one test for that feature. Write a five-line map without making changes.
If you can explain the path without saying “the framework just does it,” you are learning to read systems rather than merely read files.