Lesson 7 of 32
Your First Real Task
A complete worked task, from an empty terminal through to a commit — choosing what to attempt, writing the prompt, watching the loop, and deciding when it is done.
Six lessons of theory. Now one task, start to finish, with the reasoning made explicit at each step.
The task here is deliberately unglamorous, because the point is the shape of the session rather than the cleverness of the change. Once you have this shape, the rest of the course is refinements on it.
Choosing something to attempt
The first task you give an agent should be small, real, and checkable.
Small enough that you can read the whole diff without losing the thread. Real enough that you care about the answer, because a toy task teaches you nothing about whether this works for you. Checkable in the sense that something other than your opinion can tell you it worked — a test, a build, a page that either renders correctly or does not.
And in a project under git, with nothing uncommitted. Commit or stash first. You know why from the last lesson.
What to avoid on day one: the largest thing on your list. Not because it will fail, but because a four-hundred-line diff from a tool you do not yet trust is unreviewable, and you will end up either accepting it blindly or throwing it away. Neither teaches you anything.
- Step 1
Orient
Ask questions. Find out whether Claude has located the right parts of the project before it starts changing them. No edits in this phase.
- Step 2
Describe
One prompt with three ingredients: what should be true afterwards, where to look, and how the result can be checked.
- Step 3
Watch
Read the tool calls as they go past. Interrupt if the direction is wrong. Do not read the code yet.
- Step 4
Verify
Read the diff yourself, and run the check independently. A passing suite is necessary, not sufficient.
- Step 5
Commit
A commit ends the task and gives you the floor for the next one.
Step one: orient
Our example project is a small Next.js site with a blog. The problem: the blog index shows every post, including drafts, which is embarrassing. That is the task.
Start where the work is, and start by asking rather than telling.
WhyBefore any of this: git status was clean and the working directory is the project root. Both were checked, not assumed.
You typed: How are blog posts loaded and listed? Do not change anything yet.
Claude ran: Search: files under content/blog
Claude ran: Search: getPosts
Claude ran: Read lib/posts.ts
Claude ran: Read app/blog/page.tsx
Claude replied: Posts are MDX files in content/blog. lib/posts.ts exports getAllPosts, which reads the directory, parses frontmatter and sorts by date. app/blog/page.tsx calls it and maps over the result. Frontmatter includes a draft boolean, set on two of the fourteen posts, but nothing reads it.
WhyThat last sentence is the whole task, discovered in four tool calls. It also tells you the fix belongs in lib/posts.ts rather than in the page, which you did not know a minute ago.
You typed: Is getAllPosts used anywhere other than the blog index?
Claude ran: Search: getAllPosts
Claude replied: Three places: the blog index, the RSS route at app/feed.xml, and the sitemap.
WhyThis is why you orient. A naive fix inside getAllPosts silently changes the feed and the sitemap too. That might be what you want — but it should be a decision, not a side effect.
You typed: Are there tests for any of that?
Claude ran: Search: files named posts.test*
Claude ran: Read lib/posts.test.ts
Claude replied: Yes. Six tests covering sorting and frontmatter parsing. Nothing about drafts.
Three questions. Look at what they bought: the right file to change, a consequence you would not have predicted, and confirmation that a test file exists to add to.
This phase is cheap and people skip it. A wrong answer here costs one message. The same misunderstanding found after twelve edits costs the whole attempt.
Step two: describe the task
Now the prompt. Three ingredients, and it is worth being deliberate about all three.
What should be true afterwards. Not "fix the drafts thing" — the state you want. Drafts do not appear on the index.
Where to look. You now know it is lib/posts.ts, so say so. You are not
required to, and Claude would find it again, but you already paid for that
discovery and there is no reason to pay twice.
How it can be checked. The part people leave out. Something that returns a pass or a fail.
Put together:
Drafts should not appear on the blog index, but should still be reachable by
direct URL. The frontmatter flag is already there and unused; the loading code is
in lib/posts.ts. Keep the feed and sitemap behaviour unchanged for now — I will
decide about those separately. Add a test for the filtering to lib/posts.test.ts
and run the suite.That is longer than "hide the drafts". It is also the difference between one attempt and four.
Notice one thing that prompt does not do: specify the implementation. It does
not say to add a includeDrafts parameter, or to filter in a new exported
function. That is delegation — describe the outcome and the constraints, and leave
the mechanism to the thing that just read the file. If you have a strong opinion
about the mechanism, say it; if you do not, inventing one is how you get worse
code than you would have got by staying quiet.
Step three: watch it run
You typed: Drafts should not appear on the blog index, but should still be reachable by direct URL. The loading code is in lib/posts.ts. Keep feed and sitemap behaviour unchanged. Add a test to lib/posts.test.ts and run the suite.
Claude ran: Run: npm test lib/posts
Output: PASS lib/posts.test.ts (6 tests)WhyIt established the baseline before touching anything. Now a failure later means the change caused it, which is information you cannot get retrospectively.
Claude replied: I will add a publishedPosts export that filters out drafts, use it on the index, and leave getAllPosts as-is so the feed and sitemap are unaffected. The individual post route reads the file directly, so direct URLs keep working.
WhyRead this before letting it continue. It is a design decision stated in one sentence, and it is the last cheap moment to disagree.
Claude ran: Edit lib/posts.ts
Claude ran: Edit app/blog/page.tsx
Claude ran: Edit lib/posts.test.ts
Claude ran: Run: npm test lib/posts
Output: FAIL lib/posts.test.ts publishedPosts › excludes drafts expected 12, received 14WhyThe test it just wrote fails. Good — a test that passes immediately on a bug you have not fixed yet is not testing anything.
Claude ran: Read lib/posts.ts
Claude replied: The frontmatter parser returns draft as the string 'true' rather than a boolean, so the filter never matched. Parsing it properly rather than comparing loosely.
Claude ran: Edit lib/posts.ts
Claude ran: Run: npm test lib/posts
Output: PASS lib/posts.test.ts (7 tests)Claude ran: Run: npm test
Output: PASS 38 tests, 0 failuresClaude replied: Done. publishedPosts filters drafts and the index uses it; getAllPosts is unchanged so the feed and sitemap still include everything. One new test. Full suite green.
WhyNine actions, one self-corrected mistake, one prompt. The mistake is the part worth noticing: the string-versus-boolean bug was pre-existing and nothing else had ever exercised it.
While that is running, your job is to read the tool calls, not the code. You are watching for direction: is it in the right files, is it running the right checks, has it decided to do something larger than you asked for. Reading code as it scrolls past is a bad use of the time, and the diff is a better view of the same information in a minute.
If the direction had been wrong — say it had started editing the feed route —
Esc and a correction. That costs seconds. Letting it finish and then unpicking it
does not.
Step four: verify it yourself
The suite is green and the summary sounds right. You are not finished.
A passing test suite proves that the assertions you had still hold. It says nothing about the eleven other lines that changed on the way past. So:
git diffRead it. You are looking for three specific things, and none of them is "is this code good".
Scope. Did anything change that had no business changing? Reformatted files,
an unrelated import tidied, a version bumped in package.json. This is the most
common thing to find and the easiest to miss.
The test. Read the test that was added, not just the fact that it passed. Does it assert the behaviour you wanted, or does it assert whatever the implementation happens to do? A test written after the code, by the thing that wrote the code, is worth checking.
The claim. The summary said the feed still includes drafts. Is that visible in the diff? If a summary makes a claim you cannot see, ask about it.
Then check the thing itself, independently of anything Claude ran. Load the blog index. Count the posts. Open a draft by its direct URL and confirm it still renders. Thirty seconds, and it tests the actual requirement rather than a proxy for it.
Two questions worth asking at this point, because they cost one message each:
What did you change that I did not ask for?What would break if a post had no draft field at all?The first catches scope creep. The second catches the edge case, and Claude is genuinely good at answering it about code it just read.
Step five: commit
commit this with a message describing the draft filteringOr write the commit yourself. Either is fine. What matters is that the task ends
with a commit, because that is what gives the next task a clean floor and what
makes the next git diff mean something.
When it goes wrong instead
It will, and the recovery is the same shape every time.
Ten minutes in, the approach is clearly not right. Do not keep steering — steering
a bad plan produces a compromise shaped by the wrong starting point. Esc to
stop, /rewind to go back to before your prompt, and write a better one using
what the failed attempt just taught you. The prompt from that message is handed
back to you in the input box, so editing it is easy.
That is the loop above the loop: attempt, learn, rewind, re-describe. The reason checkpoints matter is not that they save you from disaster. It is that they make the second attempt free, and the second attempt is usually much better than the first because you now know something you did not.
What to take away
A good session has a shape: orient with questions before you ask for changes, describe the task with an outcome, a location and a way to check it, watch the tool calls rather than the code while it runs, then read the diff and verify the result yourself before committing. Orientation is cheap and repeatedly changes what the task turns out to be. Verification is where new users underinvest, because a green suite feels conclusive and only proves that your existing assertions still hold. And when an attempt goes wrong, rewinding and re-describing beats steering, because you are now writing the prompt you would have written if you had known.
Next: the constraint underneath all of this — the context window, what fills it, and what happens to your instructions when it runs out of room.
Check yourself
5 questions · pass 4/5 to unlock The Context Window
1.What makes a good first task to try with Claude Code?
2.Why ask questions before asking for a change?
3.Which of these prompts is most likely to succeed on the first attempt?
4.The tests pass and the summary sounds right. What is left to do before you commit?
5.Ten minutes in, the approach is clearly wrong. What is the best move?
5 left to answer