MemberPress AI Academy memberpress.ai

Courses

Claude Code — Getting Started

Install Claude Code, run your first task, read the diff, and learn when to interrupt and when to let it run.

intermediate2h 10m8 lessonsTaught by Seth Shoultes

SetupInstall and First Task

1. Install Claude Code 5 min

Lesson notes

Most developers install Claude Code, open it in their largest repo, and ask it to "clean things up." The session produces a 400-line diff they can't evaluate and they close the tab. The install is not the hard part. Knowing what to look at while Claude runs is.

Two install paths

The CLI and the IDE extension are separate packages. The CLI is the right starting point because every tool call is visible in the terminal. You see what Claude reads, what it proposes to change, and what it executes. That visibility is what makes the first session useful rather than alarming.

npm install -g @anthropic-ai/claude-code
claude --version
# → claude-code/1.x.x darwin-arm64 node-v20.x.x

If claude --version prints nothing, your npm global bin directory is not on PATH. Run npm bin -g to find it, then add that path to your shell profile.

Authenticating

On first run, claude opens a browser window for Anthropic account authentication. Complete the OAuth flow, return to the terminal, and you will see a confirmation line. The token is stored in ~/.config/claude/ — you do not need to handle it manually.

claude
# → Authenticated as yourname@example.com
# → Working directory: /your/project
# → >

Adding the IDE extension

The VS Code extension wraps the same CLI binary. Install it from the Extensions panel by searching "Claude Code," sign in with the same Anthropic account, and the Claude panel appears in the sidebar. The extension adds inline diff previews and a keyboard shortcut for asking Claude about selected code. Install it after you have run one CLI session — you will have context for what the extension is showing you.

Which to use day-to-day

Use the CLI when you want to see every tool call in sequence. Use the extension when you are inside a file and want to ask a quick question about a function without switching windows. They share the same session state.

If you get stuck

  • npm install -g fails with permissions error: do not use sudo. Instead, configure npm to use a user-writable global directory. npm config set prefix ~/.npm-global and add ~/.npm-global/bin to PATH.
  • The browser auth window does not open: run claude auth login explicitly. It prints a URL you can paste manually.
  • claude --version prints an old version after reinstall: close all terminal tabs and reopen. Shell sessions cache the old binary location.

Deliverable

claude --version prints a version number in your terminal.

Try these prompts

  • Write the exact shell command to install Claude Code globally and verify the install.
  • My npm global bin directory is not on PATH. Write the commands to find it and add it to my zsh profile.
  • Write the Claude CLI command that explicitly opens the browser authentication flow.

Back to contents

2. First task — let it edit a file 7 min

Lesson notes

The instinct is to start with something impressive — "refactor the auth module," "optimize the query layer." Resist that. The first task is not about output. It is about building a read-the-diff habit before you have a diff you care about.

Choosing the right first task

The task should be bounded, reversible, and not touching anything that ships today. Good candidates: rename a function across a small set of files, fix a repeated typo in a configuration file, add a missing return type annotation to one module. Bad candidates: anything touching auth, payments, database migrations, or a file with no tests.

A useful constraint: the full diff should be readable in under two minutes. If the task would take you longer than that to review, it is too large for your first run.

Starting the session

Navigate to your project root and open Claude:

cd ~/projects/my-app
claude

Then give Claude the task in a single, specific sentence:

> Rename the function `getUser` to `fetchUser` everywhere in src/. Do not touch test files.

Claude will read the relevant files, propose a list of changes, and begin executing them one file at a time. You will see each tool call — read_file, write_file, bash — printed as it runs.

Watching it run without interrupting

Let Claude complete the full task before you read anything. Interrupting mid-run leaves the codebase in a partially edited state that is harder to evaluate. When Claude finishes, it prints a summary of what changed.

Reading the diff before you accept

This is the non-negotiable part of the lesson. Run:

git diff

Read every changed line. Not the summary — the diff. Look at the function call sites. Look at any string literals that contained the old name. Check that test files are untouched if you said not to touch them.

If you see a change you did not ask for, do not accept. Type a message to Claude naming the unexpected change:

> You changed a comment in src/utils.ts that I didn't ask about. Revert that line only.

Claude will make the surgical correction.

If you get stuck

  • Claude edits test files even though you said not to: re-brief with a stronger boundary — "only files in src/, no files matching .test. or .spec."
  • The diff is much larger than expected: type Escape to interrupt, run git checkout . to reset, and narrow the brief before trying again.
  • Claude reads a file and then does nothing: it may be waiting for confirmation. Press Enter or type "continue."

Deliverable

A committed change from a real task where you read the entire git diff before running git add.

Try these prompts

  • Write a one-sentence Claude Code task brief to rename the function `parseUser` to `deserializeUser` across all files in src/, excluding test files.
  • What are three good candidates for a first Claude Code task and three bad candidates? Format as two short lists with one-line reasons.
  • Write the git command to reset all changes in the working directory after a Claude Code session produced unexpected edits.

Back to contents

3. Reading the diff 6 min

Lesson notes

Claude's summary and the diff are not the same document. The summary describes what Claude intended. The diff shows what Claude actually wrote. In a codebase where things ship, you need the diff.

Why the summary is not enough

Claude's summaries are accurate about broad strokes and unreliable about specifics. "I renamed the function and updated all call sites" is usually true. "I did not touch anything else" is sometimes not. The diff is the only authoritative record of what changed.

The 30-second scan

Before reading line by line, scan for three categories:

  1. New imports at the top of any file. An import you did not ask for is a dependency you did not choose.
  2. Deleted lines, especially deleted conditionals. A removed if block may be removing a guard you needed.
  3. Changed function signatures. A renamed parameter or a new default argument can break callers you cannot see in the diff.
git diff --stat HEAD
# → src/api/user.ts    | 4 ++--
# → src/utils/auth.ts  | 2 +-
# → 2 files changed, 5 insertions(+), 3 deletions(-)

git diff HEAD
# Full line-by-line view

Use --stat first to see which files changed. If a file appears that you did not expect — a test file, a config file, a dependency manifest — stop before reading the full diff and ask Claude why it touched that file.

What a clean diff looks like

A rename task on getUser → fetchUser across three files should produce: green lines adding fetchUser, red lines removing getUser, no changes to whitespace-only lines, no changes to imports, no changes outside the function names themselves. If the diff is tidy, it is safe to commit.

What a suspicious diff looks like

  • A restructured if/else block that was not mentioned in the task
  • A new console.log or debug statement
  • A changed default value in a function signature
  • An added or removed blank line that shifts the line numbers of everything below it (not harmful but a sign Claude did something beyond the task)

When you see anything outside scope, name it explicitly before accepting:

> The diff shows a restructured conditional in src/auth.ts line 47. I didn't ask for that. Revert it.

Cross-reference

The habit you are building here is the same one l5 ("When to interrupt, when to let it run") depends on. You can only interrupt at the right moment if you know what "outside scope" looks like in a diff.

If you get stuck

  • git diff shows nothing: Claude may have committed automatically. Run git log --oneline -3 and then git show HEAD to see the most recent commit.
  • The diff is too large to read: this is information. Reject the change, reset with git checkout ., and make the task smaller.
  • You accepted before reading: run git revert HEAD to undo the commit, then re-run the task and read the diff this time.

Deliverable

For the task you ran in l2, write one sentence describing something the diff showed that Claude's summary did not mention. If the summary was perfectly accurate, write why you trust it.

Try these prompts

  • List the three diff categories to scan before reading line by line: new imports, deleted conditionals, and changed function signatures. For each, give one example of why it matters.
  • Write the git command to see which files changed and how many lines, without showing the full diff.
  • Write a one-sentence Claude Code message to revert a single unexpected change: a restructured conditional in src/auth.ts line 47.

Back to contents

The LoopWorking With The Loop

4. Asking for a refactor 8 min

Lesson notes

"Make this code better" is not a refactor brief. It is an invitation for Claude to guess what you value — and Claude will guess confidently, wrong, and at scale.

What a refactor brief contains

A usable brief has four parts: the structural change, the motivation, the boundary, and the success criterion. Missing any one of them produces a diff that is either too narrow (Claude interprets literally and misses the obvious adjacent case) or too wide (Claude interprets liberally and rewrites the module).

Here is a brief that works:

Refactor: Extract the database calls from UserController into a UserService class.
Motivation: The controller is being unit-tested in isolation and the DB calls prevent
mocking. We want the controller to call service methods, not query the DB directly.
Boundary: Do not change the route definitions in routes/user.ts or any test files.
Success: All existing tests pass. The controller has no direct imports from src/db/.

Here is a brief that does not work:

Clean up the auth module.

The difference is not length. It is specificity about structure, not style.

Naming the structural change

"Extract X into Y," "move X from A to B," "split X into two functions," "collapse X and Y into a single pipeline" — these are structural changes. "Make it cleaner," "simplify it," "improve readability" are style preferences. Claude can execute structural changes reliably. Style preferences produce unpredictable diffs.

Stating the motivation

The motivation lets Claude make judgment calls at the decision points it will encounter inside the task. "We're extracting this because the controller is being tested in isolation" tells Claude why mocking matters. When Claude finds a helper function that straddles the controller/service boundary, it knows which side it belongs on — the side closer to the service, because that is where the DB logic should live.

Without a motivation, Claude picks the most locally plausible option, which is rarely the architecturally correct one.

Setting the boundary

The boundary is the single highest-leverage sentence in the brief. "Do not touch the route definitions" prevents 80% of scope creep. Be specific: name the files or directories that are off-limits. "Don't touch tests" is better than "leave the rest alone."

A working brief template

Refactor: [structural change — what moves where]
Motivation: [why — what problem this solves]
Boundary: [what not to touch — specific files or dirs]
Success: [observable criterion — tests pass, no import from X, function count reduced]

If you get stuck

  • Claude rewrites more than the brief asked: check whether your "boundary" sentence was specific enough. "Don't change other things" is not specific. "Don't touch files in src/routes/ or src/tests/" is.
  • Claude asks clarifying questions mid-task: answer them. The question usually means the brief had an ambiguity at exactly the place Claude is working. Answer narrowly — one sentence, not a new brief.
  • The refactor passes your review but breaks a test you did not know existed: this means the test was testing the structural coupling you just removed. Read the test. Decide whether the test was right or whether it needs to be updated to match the new architecture.

Cross-reference

l6 ("Tests as the safety rail") covers what to do when the function you are refactoring has no tests. Write the tests first. The refactor brief format here works best when there is something to verify against.

Deliverable

Write a refactor brief using the template above for one function or module in your codebase. Run it. Compare the diff against the brief. Note one way the diff diverged from what you specified and whether that divergence was correct or a scope error.

Try these prompts

  • Write a refactor brief using the template: Refactor / Motivation / Boundary / Success. The function is a 200-line authentication handler that mixes session logic with database queries.
  • Explain why "clean up the auth module" fails as a refactor brief and rewrite it as a brief that would produce a predictable diff.
  • Write the Boundary clause for a refactor that should only touch src/api/UserController.ts and create src/services/UserService.ts.

Back to contents

5. When to interrupt, when to let it run 7 min

Lesson notes

Claude Code can run for a long time on a complex task. The problem is not duration — a well-scoped task should run until it finishes. The problem is that it can also run for a long time on a task that has gone wrong, and the two situations look identical from the outside.

Three signals that the run has gone wrong

First: Claude is reading files that have no relationship to the task. A refactor of UserController does not need to read PaymentService. If you see tool calls reaching outside the stated scope, Claude has lost the thread.

Second: Claude's proposed plan is substantially larger than the brief. If you asked it to extract a service layer and it proposes to also update the router, add a new middleware, and restructure the models directory, that is scope expansion, not thoroughness.

Third: The same test failure appears three times in a row and Claude's response to each failure is a different approach rather than a diagnosis. This is the "almost done" loop. Claude is guessing rather than reading the error.

The interrupt move

Press Escape or send a new message. You do not have to wait for the current tool call to finish. Name what you are seeing specifically:

> Stop. You're reading files in src/payments/ which is outside scope. 
> Come back to the UserController refactor and ignore that directory.

Claude recalibrates from its current position. It does not start over. The context it has built up about the files it has already read is intact.

Three signals that the run is on track

First: Claude is moving through a predictable sequence — read the controller, propose the service interface, create the service file, update the imports. Each step follows from the last.

Second: The tool calls are staying inside the files you would expect. A controller refactor reads the controller, its imports, and its tests. Nothing else.

Third: When tests fail, Claude reads the failure message and changes the code at the location the failure points to. It is not iterating randomly.

When silence is not a problem

Claude sometimes pauses for several seconds between tool calls while it reasons about the next step. This is normal. The pause is not a stall. Wait for it.

If you get stuck

  • You interrupted and Claude seems confused about where it left off: give it a one-sentence summary of what it had completed before the interrupt — "You had already created UserService.ts and updated the imports. The remaining task is to remove the direct DB calls from UserController."
  • Claude keeps expanding scope even after you interrupt: add an explicit constraint to every subsequent message — "Do not touch files outside src/api/user.ts and src/services/UserService.ts."
  • You are not sure whether to interrupt: run git diff mid-session. If the partial diff is coherent and scoped, let it run. If it is sprawling, interrupt.

Cross-reference

The diff-reading habit from l3 is the prerequisite here. You can only detect scope drift by reading the diff. If you have not built that habit yet, go back to l3 before running a long task.

Deliverable

Run a task long enough to see at least three tool calls. Write down whether each call was inside or outside the scope of what you asked. If one was outside scope, note whether you interrupted or let it run, and what happened.

Try these prompts

  • List the three signals that a Claude Code run has gone wrong and the three signals it is on track. Two sentences each.
  • Write the exact interrupt message to send Claude when it is reading files in src/payments/ during a UserController refactor that should only touch src/api/.
  • Write a one-sentence summary to reorient Claude after an interrupt, given that it had already created UserService.ts and updated the imports before you stopped it.

Back to contents

6. Tests as the safety rail 8 min

Lesson notes

The thing that makes Claude Code dangerous on a codebase without tests is not that Claude writes bad code. It is that you have no way to know whether the code is bad until something breaks in production. Tests give you that knowledge immediately. They do not make Claude better — they make Claude's errors visible before they ship.

Why tests change the risk profile

When Claude edits tested code, the test suite runs after the edit and either passes or fails. A failure tells you which specific expectation was violated and which line violated it. You do not have to read the entire diff to find the problem — the test points at it.

Without tests, the diff is the only check. The diff shows you what changed. It does not show you what the change breaks downstream. Those downstream breakages are invisible until runtime.

Establishing a baseline before Claude starts

Before you give Claude a task, run the test suite and confirm it passes. This is not optional. If tests are already failing, you cannot attribute a new failure to Claude's changes.

npm test
# → Test Suites: 12 passed, 12 total
# → Tests:       87 passed, 87 total

After Claude finishes, run the test suite again. Any new failures are Claude's responsibility. Any failures that were present before Claude started are your responsibility.

# [Claude makes changes]
npm test
# → Test Suites: 11 passed, 1 failed, 12 total
# → FAIL src/api/user.test.ts
# →   ● UserController › should return 404 when user not found

That failure is the information. Read it, then tell Claude what it says:

> The test UserController › should return 404 when user not found is failing.
> The error is: Expected status 404, received 200. Fix only that case.

Writing tests before Claude touches an untested function

If the function you want Claude to refactor has no tests, write the tests first. You can use Claude to write them — but read every test before you run it. A test that always passes is not a test; it is documentation that happens to be executable.

Minimal test coverage for a function: one test for the happy path, one test for the primary error case. For a function that handles user data, add one test for an empty or malformed input.

npm test src/api/user.test.ts
# → Tests: 3 passed, 3 total

Now give Claude the refactor task. The tests will tell you if the refactor breaks the contract.

What "load-bearing" means

Payment processing, authentication, data migrations, anything that writes to a database, anything that sends an email or an external notification — these are load-bearing. They get tests before Claude touches them. Static copy, CSS, internal tooling scripts — lower risk, more tolerance for a lighter bar.

If you get stuck

  • Claude wrote a test that always passes: a test that mocks its own subject, that never asserts anything, or that catches errors silently will pass even when the code is broken. Read the assertion lines. An assertion must fail under some input.
  • The test suite takes too long to run to use as a feedback loop: run only the tests for the file Claude is editing. npm test -- --testPathPattern=user.test.ts or the equivalent in your framework.
  • Claude's change passes all tests but feels wrong: trust the feeling. Tests cover specified behavior, not all behavior. Add a test for the case that worries you, run it, confirm it passes or fails.

Cross-reference

l4 ("Asking for a refactor") covers how to write the brief that Claude will execute against your tests. The brief's success criterion should reference the test suite: "All existing tests pass" is the minimum bar.

Deliverable

Identify one function in your codebase that is load-bearing and has no tests. Write three tests for it — happy path, error case, edge case. Confirm all three pass before you run Claude on that function.

Try these prompts

  • Write the minimal test coverage for a function that validates a user's email and password on login — happy path, primary error case, and one edge case.
  • Write the Claude Code message to send when a test failure says: Expected status 404, received 200. Name the exact location to fix.
  • Explain in two sentences why a test that mocks its own subject — one that catches errors silently — will always pass even when the code is broken.

Back to contents

ShipReal Work

7. Ship a small PR end-to-end 15 min

Lesson notes

Every workflow you have practiced so far — reading diffs, writing briefs, running tests — converges here. The deliverable is not a demonstration. It is a merged PR on a real ticket that you would have shipped anyway.

Picking the ticket

Start with something that already exists on your backlog. A bug fix is ideal: the expected behavior is specified, the failure is reproducible, and the success criterion is clear. A documentation update also works. A new feature is too open-ended for a first PR with Claude.

The ticket should be small enough that the full diff is reviewable in under five minutes. If you cannot describe the change in two sentences, the ticket is too large.

Setting up the branch

Ask Claude to create the branch before it writes any code. Confirm the name before proceeding.

> Create a branch named fix/settings-cancel-button-does-not-clear-form and check it out.
> Do not write any code yet.
git branch
# → * fix/settings-cancel-button-does-not-clear-form
# →   main

Confirm you are on the new branch before you continue.

Giving Claude the task

Hand Claude the ticket text, not your paraphrase of it. If the ticket says "the cancel button on the settings page doesn't clear the form state," give Claude that sentence. Paraphrasing introduces your assumptions. The original text is the specification.

> Ticket: The cancel button on the settings page doesn't clear the form state. 
> When a user edits their profile and clicks Cancel, the form should reset to 
> its last saved values. Currently it does not.
> 
> Fix this. Only touch the settings page component and its tests.

Before you open the PR

Three checks, in order:

  1. git diff main — read the full diff against main, not just the last change.
  2. npm test — all tests pass.
  3. If the repo has a linter: npm run lint — no errors. Claude sometimes produces code that passes tests but fails the linter.

If any check fails, tell Claude what failed and what the error says. Do not describe the error in your own words — paste the exact error output.

Writing the PR description

Claude can write the PR description. Read it before you submit. The description should explain what changed and why, not list the files. If Claude produces a changelog ("Updated FormComponent.tsx, added test for cancel behavior"), ask it to revise:

> Rewrite the PR description as a narrative. Explain what the bug was, 
> what caused it, and what the fix does. One short paragraph.

Opening the PR

gh pr create --title "Fix: cancel button now resets form to saved state" \
  --body "$(cat pr-description.md)"

Or let Claude run that command. Either way, read the PR on GitHub before you mark it ready for review.

If you get stuck

  • Claude starts writing code before creating the branch: interrupt, reset with git checkout main && git checkout ., and restart with an explicit instruction to create the branch first.
  • The PR diff includes changes from a previous task: your branch diverged from main at the wrong point. Run git log --oneline main..HEAD to see what commits are on the branch and whether any should not be there.
  • Claude's PR description is a list of file names: use the revision prompt above. If it produces a list again, write the description yourself — one paragraph, two minutes.
  • The PR review catches something you missed: this is the system working. Fix it on the same branch, push, and the PR updates automatically.

Cross-reference

l3 ("Reading the diff") and l6 ("Tests as the safety rail") are the two habits this lesson depends on. If either of those feels uncertain, practice them on a throwaway change before running a real PR.

Deliverable

A merged PR with a URL. The branch was created before any code was written. The diff was read in full. All tests passed before the PR was opened.

Try these prompts

  • Write the Claude Code task message for a ticket that says the settings page cancel button does not reset the form. Include the boundary clause.
  • Write the three pre-PR checks in order — diff, tests, linter — as a short numbered checklist with the exact command for each.
  • Write a one-sentence revision prompt to send Claude when its PR description reads: "Updated FormComponent.tsx, added test for cancel behavior."

Back to contents

8. When Claude Code goes sideways 10 min

Lesson notes

Three failure modes appear often enough that naming them before you encounter them is worth the time. They have different symptoms, different causes, and different fixes. Recognizing them early saves the session.

Over-eager refactoring

Symptom: the diff is much larger than the task. You asked Claude to extract a service layer and the diff touches the router, the models, three test files, and a README. Claude interpreted the task as an invitation to improve the architecture rather than execute the brief.

Cause: the brief did not contain a boundary clause, or the boundary was too vague ("don't change other things" rather than "don't touch files outside src/api/user.ts and src/services/").

Fix: interrupt with Escape, run git checkout . to reset, and rewrite the brief with an explicit boundary. Do not attempt to "undo" specific changes from a sprawling diff — the reset is faster and cleaner.

What the revised brief looks like:

Refactor: Move DB calls from UserController into UserService.
Boundary: Only src/api/UserController.ts and src/services/UserService.ts (create it).
Do not touch src/routes/, src/models/, or any test file.
Success: npm test passes, UserController has no import from src/db/.

Hallucinated APIs

Symptom: Claude's code looks syntactically correct and logically plausible, but it fails at runtime with TypeError: X is not a function or a 404 from an internal endpoint. The method or route does not exist.

Cause: Claude's training data contains many examples of similar libraries and APIs. When an API is unfamiliar or has changed since training, Claude will sometimes generate a call to a method that it expects to exist by analogy.

Fix: ask Claude to show you where in the source or documentation the method is defined.

> You called `db.user.findByEmail()`. Show me where that method is defined 
> in this codebase or in the Prisma docs.

If Claude cannot show you the source, the method is hallucinated. Delete the generated code and re-brief with the real API reference included in your message:

> The correct Prisma method is `db.user.findFirst({ where: { email } })`.
> Rewrite the service using that call.

Including a concrete example of the real API in the brief prevents the hallucination from recurring.

The "almost done" loop

Symptom: Claude says "almost done" or "one more fix" multiple times. The same test keeps failing. Each attempt tries a different approach. None of them read the actual error message carefully.

Cause: Claude is iterating on the code based on its prediction of what might fix the problem, rather than reading the specific failure the test is reporting. This is a reasoning failure, not a capability failure — it has the information it needs, but it is not using it.

Fix: interrupt. Read the test output yourself.

npm test 2>&1 | grep -A 10 "FAIL\|●"

Then give Claude the specific failure, not a description of it:

> Stop iterating. The test failure is:
> Expected: { status: 404, body: { error: "User not found" } }
> Received: { status: 200, body: { id: null } }
> The findFirst call is returning null instead of throwing. 
> Fix the null check in UserService.findByEmail only.

Naming the specific location and the specific discrepancy ends the loop. Claude is not stuck because it cannot reason — it is stuck because it is not reading the signal you are giving it.

Recognizing which failure you are in

Symptom Failure mode
Diff is larger than the task Over-eager refactoring
Code looks right but runtime crashes Hallucinated API
Same test fails repeatedly, different attempts "Almost done" loop

If you get stuck beyond these three

Close the session. Run git checkout . to reset. Start a new session with a smaller, more bounded brief. Every Claude Code problem that resists these fixes is a brief problem, not a Claude problem.

Cross-reference

l5 ("When to interrupt, when to let it run") covers the interrupt mechanics. l4 ("Asking for a refactor") covers brief construction. The failure modes here are what those two lessons are trying to prevent.

Deliverable

Write down which of these three failure modes you have encountered — in Claude Code or in any other tool that generates code. For each one: what were the symptoms, what did you do, and what would you do differently now.

Further reading

Try these prompts

  • Describe the three Claude Code failure modes — over-eager refactoring, hallucinated APIs, and the "almost done" loop — in one sentence each, covering symptom and fix.
  • Write the Claude Code message to send when it calls db.user.findByEmail() and that method does not exist. Ask it to show you the source before proceeding.
  • Write the git commands to interrupt a sprawling session and reset to a clean state before trying a tighter brief.

Back to contents