GnothiGnothi
SeriesFieldsCommunity
Sign inGet started free

One Session, Driven by Hand

Learning your codebase, one session at a time

The core of this chapter is a single hands-on walkthrough of running one Claude Code session properly, using an ordinary TypeScript/Next.js app with Postgres behind it as the running example. It opens with the problem of a session that keeps rediscovering the same facts about a repository — how to run the tests, where things live — because nothing was ever written down for it to read. From there it builds out a full first prompt: naming the goal in outcome terms, scoping what not to touch, pointing at the real files or an existing pattern to imitate, and stating a concrete done-condition such as a test that fails without the change. It covers how to read the plan that comes back — checking the file list, the silent assumptions, and whether the approach matches the project's actual conventions — and makes the case for interrupting the moment an assumption is wrong rather than after several files have been built on top of it.

From there it turns to project memory: generating a starter file with the init command, then cutting it down to only what's true and verified — real commands, real layout, real conventions — while stripping out prose, philosophy, and anything nobody will maintain. It explains how these memory files stack rather than override each other, how nested files load lazily, and how imports work. It closes with finishing a unit of work cleanly and picking it back up, covering the continue and resume flags, the from-pr option, and the naming and forking commands that make sessions easier to find again.

This week in Claude Code

The main item is pricing: Sonnet 5's standard rates are now set at $2 per million input tokens and $10 per million output tokens, cancelling a planned increase, alongside removal of peak-hour throttling and doubled five-hour rate limits on paid plans.

A recent changelog entry adds separately configurable prompt cache lifetimes for main sessions and subagents, finer usage telemetry, an organisation-configurable model picker, and a fix for remote tool servers dropping their connection in print mode. Recent releases also brought a design-drafting command preview, a Concise output style, persistent goal conditions across resumes, and an environment variable for setting a default model. On the SDK side, the Python Agent SDK picked up in-process Model Context Protocol server support and a structured error type, and the platform Python SDK reached version 1.0 on a new HTTP transport.


Here is a thing that happens on a Tuesday. You open a session in your app's repo, ask for a small change, and watch the model spend its first two minutes working out how to run your tests. It reads the package manifest. It guesses. It tries a command that does not exist in this project. It corrects itself, gets there, and does the work. Fine. Then you finish that piece, close the terminal, come back after lunch, open a new session, ask for the next small change — and it works out how to run your tests again. Same two minutes. Same wrong first guess.

Nothing broke. That is the point. A session that keeps confidently rediscovering the same fact about your codebase is not malfunctioning; it is reporting a gap, in the only way it can. And most of the frustration people have with driving one of these sessions by hand traces back to that gap, or to its sibling: not knowing where one piece of work ends and the next one starts, so every conversation becomes a sprawling thing that has been going for four hours and can no longer remember what it was for.

This chapter is about those two things and nothing else. What the model knows about your repository before you type your first word, and where one unit of work starts and stops. They are boring. They are also the whole foundation, because every more ambitious thing you will ever wire up — anything that runs longer, wider, or with less of you watching — inherits both. Get them right by hand, in one session, and later work becomes wiring. Skip them and you will keep relearning the same lesson at higher and higher stakes.

So we are going to do exactly one sitting, start to finish. The loop itself: what a good first prompt contains, how to read what comes back before you accept any of it, and how to interrupt when it is heading somewhere wrong. Then project memory: generating the file, then cutting it down to what is actually true. Then the boundary: finishing a unit of work cleanly, and picking it up again the next morning.

The running example is deliberately unremarkable, because every command here should be one you can paste against something real. Picture the app most of us actually maintain: TypeScript on Next.js, Postgres behind it, the repository on GitHub, tests that mostly pass, and one or two conventions that exist only in the heads of the two people who wrote them. That last part matters more than it looks.

Start with the prompt, because the prompt is where most sessions are won or lost, and because the fix is not "write longer prompts."

A weak first prompt in a fresh repo looks like this: add rate limiting to the login route. It is a perfectly clear sentence to another human on your team, and it is nearly useless here, because it leaves every interesting decision unstated. Which login route, if there are two. Rate limit on what key — IP address, email, both. Stored where, given you already have Postgres and possibly already have Redis. Does this project put that kind of logic in middleware or in the route handler. And, quietly, the largest one: what will you accept as done.

A strong first prompt answers those before the model has to invent answers. It has four parts, and you can feel each one missing when it is missing.

It names the goal in outcome terms, not implementation terms: after this change, six failed login attempts from the same address in a minute should get rejected before we touch the password hash. It names the scope, which is mostly a list of what not to touch: this change should stay inside the login path, and it should not modify the session logic or the database schema. It points at the ground truth — the two or three files where the answer actually lives, or the existing thing to imitate: there is already a middleware helper doing something like this for the signup route, follow that shape. And it says how you will check: the change is done when there is a test that fails without it.

That last part does more work than the other three combined, because it converts a vague request into something with an edge. Without it, you get code and a confident summary and no way to tell whether the summary is true.

There is one more move worth making on a first prompt in a repository the session has not seen before, and it costs you fifteen seconds: ask for the plan before the code. Not as a ceremony — as a cheap read on whether the model has understood your codebase or is about to write beautifully structured code against a mental model of some other project. Tell me the files you would change and why, before you change anything. What comes back is short, and it is diagnostic.

Which brings us to the part people skip. Reading what comes back is a skill, and it is not the same as reading the code.

When that plan appears, you are not checking whether the approach is good. You are checking three narrower things, in order. First, the file list. If it names files you do not recognise, or misses the one you know is central, stop there — nothing downstream of a wrong file list is worth reading. Second, the assumptions it made silently. A good plan will say something like "I'll store the counters in Postgres since I don't see a cache layer," and that sentence is where you find out it has not noticed the cache layer. Third, the shape relative to your project's conventions. If your codebase does this kind of thing in middleware and the plan puts it in the route handler, that is not a style quibble; that is the model working from generic best practice instead of from your repository, and it will do the same thing on every task until something changes.

Then, when it does start writing, read the diff and not the summary. The summary is the model's account of its own work, and it is usually accurate, and "usually" is the problem. Specifically: check that the test it added actually fails without the change. The most common quiet failure in a hand-driven session is a test that passes both ways, which feels like verification and is decoration.

Now the correction move, which is the single highest-leverage habit in this whole chapter.

When you see it going the wrong way, interrupt. Escape stops the current turn. Do it early — at the moment of the first wrong assumption, not after four files have been written on top of it. The instinct most of us have is politeness dressed up as efficiency: let it finish, then explain what was wrong. That instinct is expensive here, for a specific mechanical reason. Everything it produces stays in the conversation. If you let it build a wrong approach across five files and then ask for a different approach, the wrong approach does not disappear; it sits in the context as a large, detailed, recently-discussed example of how to do this task. You are now arguing with your own transcript.

Interrupting early leaves a small wrong thing behind instead of a large one. And your redirect should be specific about the assumption rather than the output. Not "no, do it differently" — instead, "stop; we do have a cache layer, it's the client set up in the library folder, use that instead of Postgres for the counters." One sentence, aimed at the belief rather than the code.

Say you do that three times in a session and get a good result. Good session. Now here is the pitfall, and it is not a mistake so much as a pattern you have to learn to notice.

Tomorrow you open a fresh session, and you correct the same assumption again. And the day after, again. The cache layer thing, the test command thing, the "we don't use default exports" thing. Each individual correction is ten seconds. Collectively they are the reason people conclude that this way of working does not scale, when what has actually happened is much smaller: the session starts every morning with no memory of yesterday's corrections, and you have not written them down anywhere it can read.

That is what project memory is for, and it is a real file that you maintain by hand.

The mechanism is straightforward. Claude Code looks for Markdown files carrying project instructions in a set of defined places, and reads what it finds into context at the start of a session. The one you will spend your life editing lives at the root of your repository, named CLAUDE.md — the same file can also sit inside the dot-claude directory at the root if you prefer to keep the top level tidy. Above that, there is a user-level file in the dot-claude directory in your home folder, for things that are true of you rather than of the project. Alongside it, there is a local variant, CLAUDE.local.md, for machine-specific notes you do not want to commit — the port your local database happens to be on, that sort of thing. And organisations can install a managed policy file at a system path, which on macOS sits under Application Support and on Linux under a claude-code directory in the system config tree.

The part worth getting straight, because people assume the opposite: these do not override each other. They are concatenated. Everything found gets loaded, in order from broadest scope to most specific — managed policy first, then your user-level file, then the project file, then the local one — and across directories, from the filesystem root downward toward wherever you are working. There is no winner. There is only a stack, and if two entries contradict each other, you have handed the model a conflict and it will resolve it however it resolves it. That is the practical reason to keep the project file short and true rather than long and aspirational.

Two more mechanical facts, then we generate one. Memory files in directories below your working directory — the one you might put inside a specific package in a monorepo — are not loaded when the session starts. They are picked up lazily, only once Claude actually reads or edits files in that subtree. And a memory file can pull in other Markdown files by reference, using an at-sign path import, which resolves relative to the file doing the importing and will follow up to four hops of nesting. There is also a separate store of notes the tool generates for itself per repository, shared across worktrees, of which the first two hundred lines or twenty-five kilobytes get injected at startup — which is a nice thing to know exists, mostly so you understand that not everything in your context came from a file you wrote.

To generate the first version, run the slash command init from inside a session in the repository. It takes no arguments. It reads your source, your configuration, your test setup and your build scripts, and writes a starter memory file at the repository root describing commands, architecture and conventions as best it can infer them. There is also a command-line flag, init-only, which runs your Setup and SessionStart lifecycle hooks and exits without opening a prompt — useful later, not what we are doing now.

Here is the thing to internalise about what that command produces: it is a first draft, and it is a first draft written by something that has never run your project. It is inferred, not verified. It will be roughly seventy percent right and confidently wrong about the rest, and the wrong parts are the ones that will cost you, because a wrong fact in this file does not sit there inertly — it steers every future session.

So you edit it down. The test for whether a line belongs is narrow and slightly brutal: would a competent new engineer, joining this project on Monday, get this wrong without being told?

On our Next.js app, that means things like the actual commands. Not "run the dev server" but the specific script your project uses, including the fact that the database has to be migrated first and that the migration script is a different word than the one everyone guesses. How to run the tests — and if there are two kinds, unit and end-to-end, which one you mean when you say "the tests," because that ambiguity produces a lot of confidently passing decoration. Where things go: route handlers here, shared logic there, database access only through this one module and never directly from a component. And then the conventions that exist nowhere in the code as an enforced rule but are absolutely real — no default exports, dates always stored as timestamps with timezone, every new table gets a migration and never a hand-edit, this directory is legacy and we are not adding to it.

And then what does not belong, which is the harder half.

Not prose about the project's philosophy. Not a paragraph explaining what your app does for users. Not architecture description that reads well and changes nothing about what the model will do. Every line in this file is spent from the same context budget as your actual conversation, and a line that does not change a decision is a line that pushed out one that would have.

Not anything you will not maintain. This is the real failure mode. A file describing four services when there are now six is worse than a file describing none, because the model has no way to know it is reading history. It will act on a stale fact with exactly the same confidence as a true one. Which gives you the maintenance rule: whenever you correct the same assumption twice in two sessions, that correction goes in the file. And whenever you change how something is run, tested or structured, the file changes in the same commit. It is a committed artefact of your repository. Treat it like one.

The honest version of this file for a mid-sized app is maybe thirty to fifty lines, mostly imperative, mostly commands and prohibitions. If yours is three hundred lines of well-written explanation, you have written documentation for a human who does not exist, and you are paying for it on every single turn.

With memory in place, the last thing standing between you and a session you actually control is knowing when to stop.

A unit of work has an edge, and you should be able to say what it is before you begin: this feature is done when the rate limit works, has a test that fails without it, and the whole suite passes. When you reach that edge, finish. Commit. Do not roll straight into the next idea in the same conversation, because that conversation is now carrying every dead end and abandoned approach from the work you just completed, and it will bring all of it along to a task that has nothing to do with any of it.

Now, the next morning. You want that conversation back — you were in the middle of something, and the accumulated understanding is worth real money.

Running claude with the continue flag, or its short form dash c, resumes the most recent session in the current directory. That is the one you want when you know exactly what you are picking up: no menu, straight back in. Running it with the resume flag, or dash r, opens an interactive picker listing your previous sessions, which is what you want when it has been a few days and you are not sure which conversation held the thing you need. You can also pass a session name or identifier directly to the resume flag and go straight there. And for reviewing a pull request, there is a from-pr flag that takes a number and pulls up the session attached to it.

Two wrinkles worth knowing before they bite. Sessions created non-interactively — print mode, the Agent SDK, and loop runs — do not show up in the standard picker or in plain continue. You resume those by passing the session identifier explicitly, or by combining print mode with continue. And the lookup for resuming by identifier got noticeably better in version two point one two two three: before that, it stopped searching at your current working directory, and since then it checks the local project and its git worktrees first, then does a machine-wide search and will resolve the session if exactly one matching transcript with messages exists on the host.

From inside a live session there are a few small commands that make all of this easier, and the one that changes habits is clear with a name after it. Clearing context while naming what you just finished saves that conversation to the picker under a label you chose, which means tomorrow's list reads like your work instead of like a list of timestamps. Alongside it, rename retitles the session you are in, resume opens the switcher without leaving the terminal, and fork splits the current thread into a new session — handy when you want to try a second approach without losing the first.

The rule of thumb is one sentence, and it is worth more than the flag list: resume when you are continuing the same unit of work, start fresh when the subject changes. Continuing yesterday's rate limiting work — resume, because the context is the asset. Starting an unrelated bug in the billing code — fresh, because that same context is now purely noise, and worse, it is noise that looks relevant.

If you want the whole chapter as one sitting you can copy, it is four moves.

  1. In your app's repository, run the init slash command and let it write the starter memory file.
  2. Edit that file down to what is true and verified — the real commands, the real layout, the conventions a newcomer would get wrong — and delete the rest, then commit it.
  3. Drive one small feature through the loop: prompt with goal, scope, pointer and done-condition, ask for the plan first, read the file list, interrupt the moment an assumption is wrong, and finish at the edge you named.
  4. The next morning, resume that session with the continue flag and finish anything left over.

Nested memory files across a monorepo, and the discipline of managing a context window when a session runs long, both get proper treatment later in the show. And the very next chapter takes up the settings file, permissions, and the difference between planning and acting — the controls that sit around the loop you just ran by hand.

Which leaves the news.

The week in Claude Code changes

The biggest item is pricing, because it is the one that changes arithmetic rather than workflow. Anthropic has formalised standard rates for Sonnet 5 at two dollars per million input tokens and ten dollars per million output tokens, cancelling a previously planned rise to three and fifteen. Alongside that, peak-hour throttling has been permanently removed on the paid Claude Code plans, and the five-hour rate limits doubled, following a compute expansion. If you had shifted your heavy sessions to odd hours to dodge throttling, stop doing that — go back to working when you actually want to work.

Next, from the changelog dated the twenty-fifth of August: prompt cache lifetimes are now separately configurable, via a prompt cache time-to-live setting and a second one for subagents, so you can hold a long main conversation's cache for an hour while letting shorter side work expire in five minutes. For anyone doing what we did in this chapter — a long session, resumed the next morning — that is directly relevant to what you pay. The smallest next action is to look up the prompt cache setting in the settings documentation and try the longer lifetime on your main conversation.

The same release added finer-grained telemetry to the usage command and a configurable model picker, where an organisation can supply its own list of model identifiers to replace or extend the defaults. It also fixed a real annoyance: remote Model Context Protocol servers dropping their reconnection in print mode. If you have scripted anything against a remote tool server and quietly given up on it, that is worth retrying.

Over the preceding fortnight, releases in the two point one two three range through two point one two three nine brought a preview of a design command for drafting editable interface artboards, a built-in Concise output style, and mobile device cards for machines running remote control. Interactive fork mode is now on by default, and the desktop app gained a limit-recovery toggle that auto-continues when your limits reset. Two smaller ones that matter if you resume a lot: active goal conditions now persist and restore however you resume, which previously failed through the interactive picker, and there is a new environment variable for naming per-project transcript directories. If you find yourself repeatedly picking the same model at session start, the default-model environment variable will set it for you once.

Finally, on the Python side, the Agent SDK added support for in-process servers on the two point x version of the Model Context Protocol, exported a structured result error type, and now bundles command-line version two point one two three five. The platform Python SDK reached version one point zero on the twentieth of August, moving its transport to the second-generation HTTP client library. And reportedly, an experimental flag on the web and desktop surfaces now loads tool schemas on demand rather than pulling all of them into context up front — worth watching if you have ever felt a large tool set eating your window before you typed anything.