Vibe coding with AI course Β· Module 15: Claude Code CLI
Real Workflows with Claude Code
In this lesson8
Your feature is finished, the tests are green, the diff is clean - and still you sit there for an hour rereading your own code before you dare open the Pull Request. Then you write the documentation by hand. Then the tests. Then the commit message. That is exactly where the wind turns: most of a developer's working day is not spent writing new code, it is spent reviewing, documenting, testing and migrating code that already exists. Those are night-watch chores - repetitive, unavoidable, and perfectly automatable. In this lesson Captain Redbeard hands you seven maneuvers he actually uses on board, from the simplest to the heaviest, and every one of them fits into a single command or a short script. Two warnings before we cast off. First, every command below works with the Claude Code of today, not the one you find in three-year-old tutorials: a good half of the flags floating around the internet were never real, and learning to spot them is part of the job. Second, not one of these maneuvers asks you to paste code into a chat window. Claude Code lives in your terminal, which means it can be piped into, scripted and wired into your build server like any other command line tool.
Here is the route we sail: review first, because it is the chore you meet most often, then documentation, tests, migrations, Claude inside your own bash scripts, the automatic review on the build server, and finally the long interactive session where you and Claude work side by side. Take them in order, because each one leans on a habit learned in the one before.
Maneuver 1: The Code Review
Let us start with the most frequent chore of all: getting a file read by someone who is not you. Two building blocks before you type anything. The first is the -p flag, short for print: it starts Claude Code, asks it exactly one question, prints the answer in your terminal and hands control straight back to you. No session, no conversation - one command, one answer. The second is the @ character: inside your instruction, @path/to/file tells Claude to go and read that file itself. You never paste the contents by hand, you only give the address, like marking a point on a chart. Notice that @ lives inside the sentence you write, not in the command syntax around it, and nothing stops you naming several files in one sentence - which is how you ask for a comparison rather than a report.
# Review a single file
claude -p "Review @src/auth.ts for bugs and security issues"
# Compare two versions
claude -p "Compare @src/old-api.ts and @src/new-api.ts and suggest improvements"Here is what just happened: Claude opened the files you named, read them from top to bottom, and printed an analysis back into your terminal as ordinary text. Here is what did not change, and it is the important part: your files. The -p mode reads and answers, but by default it will not save anything on its own: in this mode nobody can approve a file change, so Claude Code refuses writes until you allow them with a flag - I will show you that flag when we get to documentation. You can therefore fire these commands at any repository without the slightest risk, even in the middle of uncommitted work, which makes this a very good habit to build early.
Remember too that the output is plain text, like the output of any other command. It can be captured into a file with a redirection such as > review.md, or handed onward to another program, and that single property is what turns Claude Code from a clever assistant into a piece of your toolchain. And if you want the review narrower than a whole file, say so in the sentence itself, because the instruction is prose and not a rigid grammar.
The Pipe: Handing Claude What Git Sees
A whole file is usually too much. What you want reviewed before a commit is not all of auth.ts, it is the twelve lines you just touched. Git already knows how to extract them, and your terminal already knows how to carry the output of one command into another: that is the pipe, the vertical bar |. It takes whatever the command on the left prints and pours it straight into the input of the command on the right. Claude Code accepts that stream - anything arriving through the pipe is added to your instruction, exactly as if you had pasted it in. As for git diff, it prints the changes you have made but have not yet staged or committed. Put the two together and you get the single most useful review of your day: a small, sharp, freshly written change, read by a second pair of eyes before anybody else sees it.
# Review your changes before committing
git diff | claude -p "Review these changes before commit"
# The same diff, with a simpler instruction
git diff | claude -p "Summarize these changes"The order of the three pieces is not decoration: source first, pipe second, Claude last. Written the other way round the chain means nothing, because Claude would have nothing to read at the moment it starts. Notice as well what is missing here: there is no --diff flag and no magic option on the Claude side. The pipe is a mechanism of your shell, not a feature of Claude Code, and that is precisely what makes it so powerful - it works with any command capable of printing text, today and in ten years.
The captain's recommendation: for anything that comes out of Git, always reach for the pipe, and keep @ for the files you name yourself. Mixing the two in one command is legal but confusing, and confusion is how you end up carefully reviewing the wrong thing. Once again nothing was written, nothing was staged, nothing was committed - you asked for an opinion and you got an opinion.
The same pipe clips onto other Git outputs, and that is where it becomes a real watch-keeping tool. git show HEAD prints the last recorded commit, message and changes included, which is exactly what you want for the autopsy of a commit that has just broken the build. git diff main...HEAD compares your whole branch against main - in other words everything your Pull Request is about to propose, not merely your last step. The three dots matter: they ask Git to start from the point where your branch left main, so you are not drowned in the commits that landed on main while you were sailing elsewhere. Both commands print to standard output like any other, so both feed the pipe without ceremony.
# Analyze the most recent commit
git show HEAD | claude -p "Analyze this commit and find bugs"
# Compare the whole branch against main
git diff main...HEAD | claude -p "Find potential bugs in this diff"In both cases the structure is identical - Git source, pipe, claude -p with the question - and only the source changes. It is one gesture applied to a different scope: a single commit, then a complete branch. Nothing is written to disk, nothing is pushed, nothing is committed. You get an opinion, not an action, and that distinction is worth keeping clear in your head every time you wire Claude into something new.
Because the output stays plain text, you can also keep it for later with a redirection, > review.md for instance, and read it at your leisure while the build runs. That is the captain's morning routine on a project he does not know yet: one branch diff, one question, one file of notes.
Reviewing a Whole Pull Request
A diff gives you the lines, but a Pull Request is also a title, a description, review comments and a history. To take one on as a whole you need an interactive session: you type claude on its own, with no instruction after it, and you get a conversation that stays open. Inside that session, commands beginning with a forward slash are not messages addressed to Claude at all - they are direct orders to the tool, the way a helm order is not a conversation with the ship. The one we want here is called /code-review (/review for short - it is the same order). With no argument it reviews your branch's commits and any uncommitted changes; give it a Pull Request number and it fetches that request's contents itself, instead of waiting for you to paste them into the chat:
/code-review 42One line, and yet it is the command the captain reaches for most often before signing off a crewmate's work. Hold on to the distinction, because it is the easiest one in the lesson to muddle: claude -p is typed in your terminal, while /code-review is typed inside a session that is already open. They belong to two different worlds, and a session command will never work directly in your shell - try it there and the shell will simply complain that no such program exists.
Here too, nothing is modified: /code-review produces remarks (unless you add --fix, which applies its findings straight away), and it is you who then decides to approve, to request changes, or to close the Pull Request. Treat what comes back the way you would treat a thorough but junior reviewer: useful on the mechanical faults, still in need of your judgement on the design.
Maneuver 2: The Documentation
Nobody enjoys writing a README, which is exactly why most of them are lying within three months. Second maneuver, then: let Claude read the code and write the documentation from what is actually there, rather than from what somebody remembers being there. One property changes everything compared with the examples above. Claude Code cannot only read your files, it can write them, so you need neither a special output option nor a redirection - you simply say, in your sentence, where the result should land. Notice in passing that the -p has gone. Without it the session stays open, which lets you follow up straight away while the file is still fresh in the conversation, and that is the right mode for documentation, because the first draft is never the one you keep.
# A README built from the project's own code
claude "Read the code in src/ and write a professional README.md
with a project description, install steps and usage examples"
# API docs from a single file
claude "Generate API documentation in Markdown from @src/api.ts
and save it to docs/API.md"The difference between those two commands is instructive. The first names no file at all: Claude explores the src/ folder on its own, decides which files matter and draws a description out of them. The second aims at @src/api.ts and dictates the destination. Which one you pick depends on how much you already know about the answer you want.
Either way you see every change in the conversation: in Manual mode Claude Code asks for your approval before it creates or modifies a file, and in auto mode, which newer versions start terminal sessions in, edits to files in your project directory are approved without asking, and the classifier reviews only riskier actions. What has not moved, on the other hand, is your source code. Generating documentation does not rewrite the functions being documented, and that is exactly what we want - the chart gets redrawn, the coastline stays where it is. If you ever see a documentation run proposing an edit to a source file, refuse it and reread your instruction, because something in that sentence was more ambitious than you intended.
As long as you are documenting one file, the command is enough. The day you want to regenerate a whole project's documentation before a release, you will not retype three instructions from memory - you put them in a script. A bash script is nothing but a text file holding commands, executed from top to bottom. Its first line, #!/bin/bash, tells the system which interpreter to use; lines starting with a hash are comments and are ignored when it runs; and echo prints a message so you can see where the ship has got to. Let us call this one generate-docs.sh, and read it as three instructions in a row, because that is all it is. On Windows, run scripts like this in Git Bash or WSL, because plain PowerShell does not understand bash syntax.
#!/bin/bash
# generate-docs.sh
echo "Generating documentation..."
# The API docs
claude -p "Read src/api/ and generate complete API documentation:
endpoints, request and response types, usage examples, error codes.
Save the result to docs/API.md" --permission-mode acceptEdits
# The component docs
claude -p "Read src/components/ and document every component:
purpose, props interface, code example.
Save the result to docs/COMPONENTS.md" --permission-mode acceptEdits
# The project README
claude -p "Read the whole src/ folder and write README.md:
description, install steps, quick start, project structure.
Keep it readable for beginners" --permission-mode acceptEdits
echo "Done."Three instructions, three documentation files, one launch with bash generate-docs.sh. Notice that the -p has come back: inside a script there is nobody sitting at the keyboard to answer questions, so you want the non-interactive mode that takes the request, does the work and returns control. New here is the --permission-mode acceptEdits flag: in -p mode nobody can click to approve a write, so without it Claude would, by default, read the code but create no file at all. The acceptEdits mode lets it create and edit files without asking, so run such a script only in a repository where everything is committed to Git. The other way is to ask for the content only and redirect it into the file: claude -p "..." > docs/API.md. What has not changed compared with the manual version is the wording of the instructions themselves - a script is not a new language to learn, it is the memory of your good commands.
If the file refuses to start, it is almost always because it is not executable, and chmod +x generate-docs.sh settles that once and for all, after which ./generate-docs.sh works just as well as bash generate-docs.sh. Keep the script in the repository next to the code it documents, and the whole crew inherits your morning routine without you having to explain it.
Maneuver 3: The Tests
Code without tests is a ship without watertight bulkheads: everything is fine until the first leak. Third maneuver: have the tests written for you. Two names to know before the command. Jest is the most widespread test runner in the JavaScript world - the program that finds your test files, runs them and tells you what failed. React Testing Library is the library that lets you test a React component the way a user meets it, by looking for text on the screen rather than poking around inside the component's internals. In your instruction, always say three things: which file to test, what you want covered - edge cases and error handling above all - and where the result should be saved. That third one matters more than it looks, because a test file in the wrong folder is a test file that never runs.
# Tests for a utility module
claude "Write Jest tests for @src/utils.ts, cover edge cases
and error handling, and save them to src/utils.test.ts"
# Tests for a React component
claude "Write React Testing Library tests for @src/components/Button.tsx
and save them to src/components/Button.test.tsx"You get a complete test file written in the project's own conventions: same imports, same block names, same style, because Claude read the neighbouring files before writing this one. One captain's warning, all the same. A generated test proves nothing until you have run it. Run it, check that it passes, then deliberately break the function it covers and see whether the test falls over. A test that stays green no matter what you do to the code is worse than no test at all, because it hands out confidence without guaranteeing anything, and a crew that trusts a broken alarm sails straight onto the rocks.
What has not changed here: src/utils.ts itself is untouched, and only the test file comes into the world. That is also why you can safely generate tests for code you did not write and are afraid to modify - the risky part of the operation simply is not happening.
One file at a time is fine to get started; on a project that has been at sea for two years you want a drift net. Here are the four tools the next script uses. find src -name "*.ts" walks the folder, subfolders included, and brings back every file whose name ends in .ts - note that this pattern does not catch .tsx files, so add a second search if your components need one - while ! -name "*.test.ts" throws back the ones that are already tests. The loop for ... do ... done repeats the same treatment for each file found, parking it each time round in the variable FILE. The expression ${FILE%.ts} cuts the .ts suffix off the end of the name, so I can glue .test.ts on in its place. Finally [ -f "$TEST_FILE" ] asks whether that file already exists, and continue skips to the next turn of the loop without doing anything. None of the four is a Claude Code feature - they are ordinary shell, which is exactly why Claude Code is so pleasant to build with.
#!/bin/bash
# generate-tests.sh
# Every .ts file in the project, tests excluded
for FILE in $(find src -name "*.ts" ! -name "*.test.ts"); do
# The name of the test file we expect
TEST_FILE="${FILE%.ts}.test.ts"
# Skip files that already have a test
if [ -f "$TEST_FILE" ]; then
continue
fi
echo "Missing test for: $FILE"
claude -p "Write complete Jest tests for @$FILE.
Cover every exported function, edge cases and error handling.
Follow the Arrange-Act-Assert pattern.
Return only the test code, without Markdown fences." > "$TEST_FILE"
doneFollow the script through: for each file in src/, it works out the name of the matching test, checks whether that test exists, and calls Claude only when it does not. Claude's answer lands in the file through the > "$TEST_FILE" redirect, because by default Claude will not save the file on its own in -p mode. The second detail to hold on to is the @$FILE slipped into the instruction. Your shell replaces the variable with the real path before Claude ever sees the sentence, so the @ reference points at the right file on every turn of the loop, and Claude simply receives a finished sentence with a literal path in it.
What does not change: the tests you wrote by hand. The script spots them and sails past, so you can rerun it as often as you like without flattening your own work. That property is precious the moment a script starts talking to an AI - point it at one folder before you point it at the whole repository, and read the first generated file properly before you trust the rest.
Maneuver 4: Migrations and Refactoring
Fourth maneuver, the one that wins you whole weeks: the migration. Converting a JavaScript file to TypeScript, modernizing an old React component, replacing a library its author abandoned three years ago - these are mechanical transformations, but endless ones, and they are exactly the kind of work where human attention collapses around the tenth file. The recipe has two ingredients: the target file, named with @, and an instruction saying what must change and, above all, what must stay identical. That second half is the important one. Without it a conversion will happily simplify a behavior on the way past, because convert this file does not tell anybody that the strange-looking branch on line forty exists for a reason.
# JavaScript to TypeScript
claude "Convert @src/legacy.js to TypeScript: add types and interfaces,
keep the behavior exactly the same"
# An old component to modern React
claude "Refactor @src/old-component.jsx into a modern React function
component with hooks"For a single file that command is enough, and you see the result immediately, side by side in the diff. Verify it the ordinary way, by running the existing tests against the converted file, because the migration you can prove is the only one worth having. For a migration touching thirty files, change tactics and ask for a plan first: Claude Code has a planning mode in which it surveys the ground, proposes a course of action and waits for your go-ahead before it modifies anything at all, and you will meet it properly in the next lesson.
The captain's recommendation: past three files, always go through the plan. Reading a ten-line strategy costs you a minute; reading thirty rewritten files in one lump costs you an evening, and by file fifteen you will be approving changes you have quietly stopped reading. What does not change in either case is your version control - commit before you start a migration and the worst outcome available to you is git checkout . and a slightly wounded pride.
Maneuver 5: Claude Inside Your Own Scripts
Fifth maneuver, and the captain's favourite: commit messages. You know how a rushed evening ends - fix, fix2, actually fix. Because claude -p returns plain text, that text can be captured into a variable and reused by the rest of a script, and once you can do that, Claude stops being a thing you talk to and becomes a step in a pipeline.
Four pieces of bash to know before you read what follows. The construction $(command) runs the command and is replaced by its output, which is how you park a result in a variable. git diff --cached shows only what you have added to the staging area with git add, which is the right scope for a commit message. The test [ -z "$DIFF" ] asks whether a variable is empty, and exit 1 stops the script with an error code. Finally read -p asks a question and puts the answer in REPLY, which is then compared with [[ $REPLY =~ ^[Yy]$ ]] - in plain words: does the answer start with y or Y. At the end, git commit -m records the commit with the message you hand it. The script is called smart-commit.sh and it has exactly one job: propose a message, wait, and commit only if you say so.
#!/bin/bash
# smart-commit.sh
# What is waiting in the staging area
DIFF=$(git diff --cached)
if [ -z "$DIFF" ]; then
echo "Nothing staged. Run git add <files> first."
exit 1
fi
# The diff goes down the pipe, the message comes back
MESSAGE=$(echo "$DIFF" | claude -p "Write a commit message in
Conventional Commits format (type: description).
Return ONLY the message, nothing else.")
echo "Proposed commit message:"
echo "$MESSAGE"
echo
read -p "Use this message? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
git commit -m "$MESSAGE"
echo "Committed."
else
echo "Aborted."
fiFollow the thread; it reads like a maneuver in four movements. The staged diff goes into the variable DIFF. If it is empty, the script stops cleanly with a useful message instead of calling Claude for nothing. Otherwise the diff travels down the pipe to claude -p and the answer is captured in MESSAGE. Finally the script shows you the proposal, waits for your agreement, and commits only if you answer y.
Those four movements are the order worth memorising, because every automation you write from here will have the same skeleton: gather the input, guard against the empty case, ask Claude, confirm before acting. What has not changed, and never should: nothing is committed behind your back. The human confirmation stays right in the middle of the chain, and the captain's advice is to keep that rule in every script you build - automation proposes, the sailor decides.
Temperature, Effort and Model: What You Set and What You Do Not
A word about a few settings you have certainly heard of, because a great many old tutorials talk complete nonsense about them. Temperature is a sampling parameter of language models: lower values give more repeatable answers, higher ones more varied ones. You cannot set it in Claude Code - there is no --temperature flag and no --max-tokens flag - and the newest Claude models do not accept it even through the API (they only take the default value of 1.0). If a tutorial has you typing claude --temperature 0.2, it belongs to another era, and the same goes for the imaginary --diff flag in the same articles. The real dial next to the model is the effort level: it decides how long the model thinks about each step. You set it with the --effort flag at launch or the /effort command inside a session, with the values low, medium, high, xhigh and max. Low effort is faster and cheaper on simple tasks, high effort gives deeper reasoning on hard ones:
# A simple question: low effort
claude --effort low -p "Explain what git rebase does"
# A hard problem: high effort
claude --effort high "Find the cause of the memory leak in @src/server.ts"The choice of model is just as much in your hands, and it genuinely changes the outcome. Four families sail together, from the lightest to the most powerful. Haiku is the ship's boat: the fastest and the cheapest, cut out for short questions such as decoding an error message or explaining what a command does. Sonnet is the ship of the line, the balance between speed and depth, good for everyday work and for code review. Opus is the flagship, strong over long reasoning and the default model on most plans. Fable is the galleon for the longest expeditions: the most capable, but slower and more expensive, for migrations and whole-project rebuilds that run for hours. You pick a model with the --model flag on the command line, or with the /model command inside an open session.
# Quick question: Haiku, the fastest
claude --model haiku -p "What does this TypeScript error mean?"
# Code review: Sonnet, the balanced choice
git diff | claude --model sonnet -p "Review these changes"
# Complex refactor: Opus
claude --model opus "Refactor @src/legacy-cart.js and modernize it"A simple reflex to keep you straight: the more reasoning a task demands, the further up the range you climb; the shorter and more repetitive it is, the further down you drop. Notice too that the flag sits happily in the middle of a piped command, because it is just another argument.
The captain's recommendation: switch to Sonnet or Haiku for simple, repetitive tasks, and reach for Opus and Fable when the task really demands reasoning. Routing all your work through the biggest model costs more and takes longer without adding anything on simple questions, while running everything through Haiku has you rereading answers twice. One last piece of caution: beware the frozen model identifiers that old scripts drag along, things shaped like claude-3-sonnet-20240229. Those versions age and are eventually retired, whereas the short names haiku, sonnet, opus and fable always hand you the current model of the family.
Maneuver 6: Automatic Review on the Build Server
Sixth maneuver: hand the first read-through to the machine, so that the human crew only opens the Pull Request once the obvious faults have already been flagged. GitHub Actions is GitHub's automation system: you drop a YAML file into .github/workflows/ and GitHub runs what it contains on every event you declare. The line on: pull_request therefore means on every Pull Request opened or updated against main.
A job is a sequence of steps running on a fresh machine, chosen by runs-on, and each step does exactly one thing - either by reusing a ready-made action with uses, such as actions/checkout to fetch the code or actions/setup-node to install Node.js, or by running commands yourself with run. The API key is never written into the file: it sleeps in the repository secrets and is injected only when it is needed, through env. And here is the last ingredient, the one that really matters for automation: the --output-format json flag asks Claude Code to answer in a structured format a program can work with, instead of the text meant for a human reader. It also accepts text and stream-json, but json is the one you want whenever another step has to read the answer. Read the file below as a story in four chapters, and pay attention to their order.
# .github/workflows/claude-review.yml
name: Claude Code Review
on:
pull_request:
branches: [ main ]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Review changed files
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
git diff origin/main...HEAD | \
claude -p "Review these changes for bugs and security. Be brief." \
--output-format json > review.json
- name: Post as PR comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('review.json', 'utf8'));
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: data.result
});Read the sequence of steps again: it tells a story in which no chapter can change places. You check out the code, you install Claude Code, you review the changed files, and only then do you post the result as a comment on the Pull Request. You do not review code you have not fetched, and you do not comment on a review that does not exist yet.
Two details are worth the detour. The line fetch-depth: 0 asks for the repository's full history, without which the comparison against main would have nothing to get its teeth into, because a shallow clone does not contain the commit where your branch diverged. And review.json is not text meant for you: it is the structured file the following step opens in order to pull the result field out of it before posting. That is the whole reason the JSON format exists, and it is why the last step can be five lines of JavaScript instead of a fragile pile of text wrangling. What has not changed: the robot comments, it merges nothing and it blocks nobody.
Maneuver 7: The Pair Session
Seventh and last maneuver, the hardest one to show on paper because it is a conversation. Launch claude with no instruction after it and you open an interactive session in which Claude keeps everything the two of you have said. You describe an intention, it writes straight into the files; you ask for an improvement, it modifies what it has just written; you ask for tests, it creates the test file.
The rhythm is the rhythm of a pair: small steps, a check after each one, and never ten requests stacked up at once. That last rule is the one beginners break, and the punishment is always the same - a mountain of changes nobody can review, produced faster than any human can read it. In the block below, the lines starting with a chevron are what you type, and the ones in square brackets are what Claude does in return. It is a transcript, not a script you can run: the first line is the only real command in it, claude, alone, with nothing after it. Watch how each request quietly assumes the one before it.
# Open the interactive session
claude
# Then, inside the session:
# > I am building a login system. Start with the login function
# > in @src/auth/login.ts
# > [Claude writes the code straight into the file]
# > Now add password hashing
# > [Claude updates the same file]
# > Add rate limiting to block brute force attempts
# > [Claude adds the protection]
# > Write tests for all of this
# > [Claude creates the test file]Four requests, four modifications, and at no point did you open the editor yourself. What did not change between two messages is the context: Claude remembers the login file, the way it wrote it and the choices already made, which spares you re-explaining everything at every turn. That is why the second request can simply ask for password hashing with no file name attached - the file is already in the conversation.
That comfort has a flip side. After a long session the context fills up with old business that has nothing to do with the task in hand, and answers start referring to a decision you overturned an hour ago. Two commands put it right. /clear starts from a blank page, which is what you want when you change subject completely, for instance when the login system is finished and you move on to the payment screen. /compact keeps a summary of the conversation and carries on from there, which is what you want when the task continues but the history has grown long. The captain's habit is simple: /clear between tasks, /compact inside a long one, and neither of them touches your files.
The Ship's Log
Code review - claude -p "Review @file", or git diff | claude -p "...", and /code-review <PR number> for a whole Pull Request
Documentation - ask Claude to read the code and write the file itself
Tests - claude "Write Jest tests for @file and save them to ..."
Migrations - the file with @, and planning mode as soon as the job grows
Scripts - claude -p for non-interactive runs, the pipe | on the way in, a variable on the way out
CI/CD - claude -p --output-format json inside GitHub Actions
Pairing - an interactive session, with /clear and /compact to keep the context sharp
Models and effort - haiku to move fast, sonnet for everyday work, opus and fable for the heavy work; --effort sets how long the model thinks
In the next lesson we set a course for CLAUDE.md and planning mode, and you will see that so far we have only been hugging the coastline. See you there!
Hold on to this above all: Claude Code does not replace the sailor at the helm, it holds the ropes while you keep the course - and a crew that automates its night-watch chores always makes port ahead of the rest.
Code for this lesson: review-staged.sh
#!/bin/bash
# review-staged.sh - review staged files with Claude Code (the real way)
# Usage: ./review-staged.sh
echo "Running Claude Code review on staged changes..."
# Collect staged code files
STAGED=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx|js|jsx)$')
if [ -z "$STAGED" ]; then
echo "Nothing to review"
exit 0
fi
# Pipe the staged diff straight into Claude (no made-up file or temperature flags)
git diff --cached | claude -p "Review this diff for:
- Bugs and logic errors
- Security vulnerabilities
- Performance issues
- Best practices violations
Be concise. Only report real issues with a severity level."
echo "Review complete!"Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which Claude model for which task?
2. Which in-session command reviews a Pull Request?
These are 2 of 5 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Horizontal ordering
Arrange the command that pipes a Git diff to Claude to summarize it:
- Code editor
In commands.sh write the generate-docs.sh script that uses claude -p to create three files: docs/API.md, docs/COMPONENTS.md and README.md. Save each answer to its file with the > redirect.
- Horizontal ordering
Arrange the command that reviews an entire branch against main via a pipe:
- Vertical ordering
Put the steps of a GitHub Actions workflow with Claude review in order:
- Code editor
In commands.sh write the generate-tests.sh script: find the .ts files in src (find), skip those that already have a .test.ts file ([ -f ... ]) and call claude -p for the rest, asking for Jest tests.
- Vertical ordering
Put the steps of the smart-commit.sh script that generates a commit message in order:
- Horizontal ordering
Arrange the command that pipes a specific commit (HEAD) to Claude for analysis:
- Vertical ordering
Put a typical Claude pair programming session in order: