Roman Kryvolapov Engineering Blog

Claude Code — Best Practices

Hi!

This used to be an article about working with Cursor. I rewrote it.

Not because Cursor got worse. The way of working changed: the agent is no longer "smart autocomplete" but an executor whose workplace you prepare, and how well that workplace is prepared affects the result far more than how elegantly you phrased the request.

What follows is about Claude Code. What to put in the repository, how to plan work, how to avoid burning through context and money. The examples come from a working project where all of this is set up and has survived several months of real development; the numbers come from there too, not from my head.

It all comes down to context

The model has a context window, and the entire session fits into it: your messages, every file read, the output of every command, including that three-hundred-line log the agent printed once and never looked at again. A single debugging session easily eats tens of thousands of tokens.

The problem isn't even that the window runs out. Quality drops long before that: towards the end of a long session the agent starts forgetting early agreements and makes more mistakes, because the instruction that matters drowns among hundreds of kilobytes of code it has read, and no amount of insistence in your phrasing cures it.

Hence the conclusion everything else grows from. Context must be managed deliberately: you decide what goes in and what doesn't. Not "let it pile up, it'll compact eventually".

Give the agent a way to check itself

This is the first thing worth setting up, and the thing most often skipped.

The agent stops when the work looks done. If there is nothing to check it with, "looks" is the only signal available, and you become the checker. Every mistake waits for you to notice it.

Give it something with an unambiguous pass/fail and the loop closes by itself. The agent edits, runs the check, reads the result, repeats. The check can be a test suite, a build exit code, a linter, a script that diffs output against a fixture, a screenshot to compare with a design.

The difference shows up right in how you phrase the task:

✗ write an email validation function
✓ write an email validation function. Examples: [email protected] is valid,
invalid is not, [email protected] is not. After implementing, run the tests
and show the output
✗ the build is failing
✓ the build fails with this: [error text]. Fix the cause, not the symptom,
and make sure the build passes

In my project the check is baked into the commit rule: before a commit the type check, the build and two content validators are run without exception. Not "preferably" but "always", with a note that the gate results go into the report as they really are, not as "everything looks green".

And ask for evidence: test output, the command itself, a screenshot. Reading evidence is faster than re-checking by hand. And for a session you weren't watching it is the only way to know what happened.

Explore first, then plan, then code

Ask for code straight away and you get a tidy solution to the wrong problem.

Claude Code has a separate planning mode: the agent reads files, answers questions, changes nothing. The working cycle comes out like this: understand the relevant part of the code → draw up a plan → leave planning mode → implement → commit.

Read the plan with your own eyes. It is the cheapest point of intervention: fixing a paragraph of a plan takes a minute, redoing an implementation takes half an hour.

For a big task the "let the agent interview you" trick works well. You describe the idea in one paragraph and ask it to ask questions: about the implementation, the edge cases, the things you might have missed. At the end it writes a specification into a file. Then a fresh session with clean context and a request to implement from that spec.

I want to build [one sentence about the feature]. Interview me in detail,
using the question tool.
Ask about implementation, UI, edge cases, risks and trade-offs. Skip the
obvious, dig into the hard parts I might not have considered.
When we're done, write the full specification into SPEC.md.

Just don't turn it into a ritual. If an edit can be described in one sentence, no plan is needed: the extra round costs more than it returns.

Three levels of configuration

Knowledge reaches the agent in three ways, and they are not interchangeable.

Project instructions are read at the start of every session. Facts that are always needed go here: how to build, how to run the tests, how this project differs from a typical one. Anthropic's recommendation is to stay under two hundred lines, and that isn't a formality. The longer the file, the worse its own instructions are followed.

A good test for every line: if I remove this, will the agent start making mistakes? No — remove it.

Rules live as separate files in the rules directory, one file per topic, and load the same way. They can be scoped by path patterns, and then a rule is pulled in only when the agent touches matching files:

.claude/rules/api-endpoints.md
---
paths:
- "src/api/**/*.ts"
- "apps/backend/**/*.ts"
---
# Rules for the API
- Every endpoint validates its input, no exceptions.
- Errors are returned in the shared `ErrorResponse` format.
- A new endpoint is described in api.md first, then written in code.

Skills are knowledge on demand. A directory with a file inside; the body enters the context only when the task matches it or when you invoke the skill yourself. That is why big reference documents should be skills: while dormant they cost nothing.

The layout I use:

  • a fact needed always → project instructions;
  • mandatory behaviour → a rule;
  • a procedure or reference needed sometimes → a skill;
  • something that must happen every time, without the model's involvement → a hook.

The last point stands apart. Everything above is text the model reads and usually follows. A hook is code that runs regardless of its decision. Critical behaviour should be a hook, not a paragraph in the instructions. I go through hooks, every event and working examples, in a separate article.

And all of it is prompts

A thought I arrived at late, and should have started with.

Project instructions, rules, skills, subagent descriptions — none of that is configuration. Those are system prompts that somebody (you) writes once and the model reads every session. The difference between "the agent constantly does the wrong thing" and "the agent behaves predictably" is very often the difference between a sloppily and a carefully written prompt.

Which prompt-engineering techniques actually pay off in these files:

  • Structure and delimiters. Headings, lists, explicit blocks: the model parses structured text more reliably than solid prose, and you gain the ability to reference a specific chunk from another rule. More on delimiters and prompt structure.
  • A hierarchy of rules. Past five rules they start to conflict, and you have to state outright which one wins. In my setup "don't commit unless asked" sits above the pre-commit review rule, and the text says so in words rather than implying it. There is a separate section on setting up a rule hierarchy.
  • Positive phrasing instead of prohibitions. "Leave the changes in the working tree and describe what you did" works better than "don't commit". Prohibitions alone leave the model without a model of behaviour.
  • Examples instead of descriptions. A "bad like this / good like that" pair in a rule is worth three paragraphs of explanation. It is ordinary few-shot, just applied to a project's configuration.
  • An explicit output format. If you want a particular shape of report, it has to be described, and better still shown. On output format control, same place.
  • Context economy. Every line of instructions and rules is paid for in every session, so a superfluous word here costs more than in an ordinary prompt. This is exactly what the prompt article calls context engineering.

One more observation from practice: describe not only what to do but which mistake the rule prevents. A model that understands what it is being insured against applies the rule far better to a situation the text doesn't spell out. That is why half of my rules open with a paragraph along the lines of "the known failure looks like this".

The full walkthrough of the techniques is in the article on prompt engineering; further down I'll be linking to specific sections of it.

What it looks like on a live project

From here I'm showing a working repository. It currently holds nine rules, an API reference, a hundred and two skills and a task tracker right inside the project. None of it appeared at once; it grew out of situations where the same thing had to be explained for the third time.

Rules

A rule is what the agent must always follow. Each lives in its own file and ends with a checklist that shows whether it was followed or not.

Here is an entire rule, the shortest of mine:

.claude/rules/never-commit-unless-asked.md
# No commits until asked
**Do not run `git commit` (or push, PR, merge) until the user has asked
for it directly, in their own words.**
Finished work, a green build and a feeling of "seems done" are not
permission.
Made the edits — leave them in the working tree, say briefly what changed,
and stop. Not sure whether you were asked — don't commit, ask.
## Checklist
- [ ] No commit, push or PR without an explicit request in the user's words.
- [ ] Work left in the working tree, the changes described.
- [ ] When unsure, a question was asked instead of a commit.

Looks like overkill. Right up to the first time the agent decides that green tests mean it may as well commit, and carries your half-finished experiments into history.

Now the other eight. My originals are written in English (a historical accident, the rules travelled between projects), and the quotes below are from them.

The pre-commit review

The longest rule; it comes into force once a commit has actually been asked for. Four stages: read the whole diff hunk by hunk; examine everything the changes touch beyond the diff, meaning callers, contracts and the documentation those edits have just made stale; hunt for defects; pronounce a verdict.

There are exactly three verdicts, worded so the agent has nowhere to wriggle:

PASS — nothing found, or everything found is already fixed and re-verified.
FIX, THEN PASS — real but fixable findings. Fix them yourself,
immediately, re-run the review on the fixed state and tell the developer
plainly what was found and what was fixed. Fixing is the default:
problems are never parked "for later" and never merely
mentioned in the report.
STOP — a serious problem (data loss, a security hole, a broken
contract, the change contradicting its own requirement) or a genuine
fork where both designs are defensible and the choice is the developer's.
No commit. Explain simply and concretely what the problem is.

Then comes the sentence I consider the most useful in the whole rule: a fix made during the review is itself a change and goes through the same four stages again. Repeat until a full pass finds nothing new. Only that state counts as clean.

It also states that --no-verify is forbidden: a failing hook gets fixed, not silenced.

A word about the technique at work here. The stages are numbered, each has its own list of what to check, and at the end a mandatory one-word verdict. That is the same output format control as in ordinary prompts: it is much harder for a model to "sort of check" and say "looks fine" when it is required to pick one of three labelled outcomes.

Response style

The rule about how to talk to me. It opens like this:

The user is a human reading the chat, not Claude Code.
He does NOT have the source open in parallel and does NOT want to decode
code identifiers, field names or file paths inside an answer.
Talk to him like a colleague, not like a code review tool.
Assume the reader is a competent developer who is NOT deep in
this project. They can code, but are probably juggling several
projects at once and don't carry this one's specifics in their head.

Then a list of what is forbidden by default: code snippets, "file:line" citations, field and class names, big tables, ASCII diagrams. All of it is allowed only when I ask directly.

A separate part describes the shape of the final report: done / left to do / what I need from you. And there, an example taken from life:

✗ "Left: our own migration tree with a drift check, image and manifest,
wiring in the launch script."
✓ "Left to do:
- Describe the database changes as separate migration steps: right now
the table is created on the fly and won't exist on a real server.
- Build the image and add it to the cluster description, or there is nowhere to deploy it.
- Wire the service into the launch script so it comes up with the rest."

Three unrelated pieces of work crammed into one line, in words that mean something only to whoever just wrote them. The reader nods without reading, and later it turns out they agreed to something they never understood.

The bad/good pair works better here than any description, and it is precisely few-shot: one shown example replaces a paragraph about self-sufficient phrasing.

Session budget

Spend tokens in proportion to the task's real complexity — the cheapest
path that reliably solves it, not the most thorough one the budget
would allow. Limits are a guardrail: they show where you would hit a wall
or a rate limit so you can stop short, not a budget
that has to be used up.
A GREEN zone does NOT mean "burn freely".

The rule then carries a table of task classes: trivial — edit straight away, no thinking, no exploration; small — targeted reads, everything in one thread; medium — one planning pass and high effort only on the hard fork; large — in phases, with a checkpoint between them.

And a separate section on subagents with the arithmetic spelled out: a fan-out of N agents costs as much as N sessions, so spawning them makes sense only for a clear saving. This rule works in tandem with the hook that brings in live limit figures — that one is covered in detail in the hooks article.

When in doubt, search the web

A short rule, seventeen lines, and nearly all of it describes one scenario:

The known failure pattern to avoid: something doesn't work →
you apply a fix based on what you "know" → it still
doesn't work → each new fix digs deeper into a solution built
on a wrong assumption.
Therefore in ALL doubtful cases — when something is unclear, non-obvious,
behaves differently than you expected, or contradicts your knowledge —
ALWAYS search the web before fixing anything further, and look first
at the most recent official documentation.
If even the search gives no answer — STOP and ask the user.

Note the structure: the failure first, the requirement second. That is what lets the rule apply to situations it never spells out, because the model understands what it is being insured against.

Pull the branch before working

The cost of skipping it is not a merge conflict at the end. It is work
built on a base that no longer exists: a bug already fixed by someone else;
a review of code that has since changed; a tracker id or a migration
timestamp already taken; a decision made against a two-week-old file.
None of that surfaces until the work is finished.

Then three steps: fetch and see how far the branch is behind; fast-forward; read what arrived if it touches your area. Plus a separate section for when the working tree isn't clean.

Stop at architectural forks

Probably the most valuable rule of them all. It opens with a formulation I like:

Some decisions are an edit. Some are a rewrite. This rule is about the second.
When you notice that a piece of work can reasonably be built in more than
one way and the choice is expensive to undo, stop before writing code,
study the whole picture and put the fork in front of the developer
with the background and the options laid out.
Choosing silently and moving on is the failure this rule
prevents: its cost does not show up in the diff, it shows up months later
as work that has to be thrown away.

Then the signs by which a decision counts as a fork: hard to reverse; spreads beyond one component or team; fixes who owns what and where the trust boundary sits; changes what is visible from outside (a deployed component, a data model, an API contract); touches money, security or privacy. Two signs or more means stop.

And here is the second half of the rule, without which the first one is harmful:

Raising a fork costs the developer real attention. Before you do, check:
— Reversible within a day? → decide yourself.
— Has the project already made an equivalent decision? → follow it;
a precedent beats a preference.
— Would you be content with any of the options? → not a fork, a taste. Choose.
— Is it unclear only because you have not looked? → look first.
Most "forks" dissolve on reading.
Expect a handful of real forks in a project, not one per task.
If you are raising them weekly, the filter is wrong.

Without that filter the rule turns into its own opposite: the agent starts asking about everything, the human stops reading the questions and misses the one that mattered.

Symbol navigation

The project has a server wired in that provides go-to-definition, find-references and symbol-level editing. The rule requires using it instead of reading whole files, and its centrepiece is a mapping table:

| Task | Tool | Instead of |
| Understand a file's API | symbol overview | reading whole file |
| Find a class or function | symbol search | grep by name |
| Read one symbol's body | symbol with body | read with offset |
| Find who calls it | reference search | grep by name |
| Rename across the project | symbol rename | dozens of edits |

Then a section titled "when the standard tools are the right choice". That is the important half: a language server has languages it doesn't understand, and listing them honestly is more useful than demanding the impossible.

Work tracking

The ninth rule is about the tracker: what becomes a task or a bug, what never does, how entries relate to requirements. It is a big one, and I go through it below in its own section, because what's interesting there is the scheme rather than the wording.

Skills are documentation

The main point of this section: skills in my setup are not "plugins for the agent" but the project's documentation. The same documentation that used to go into a wiki nobody opened.

Documentation in a wiki lives apart from the code and slowly drifts away from it. Documentation in the repository is edited in the same pull request as the code, and the executor reads it. Selectively, at that: a skill's body enters the context only when the task matches it. Twenty-six thousand lines of documentation cost nothing until they are needed.

The prefix in the name is the audience. Skills are grouped not by topic but by whose team they address:

.claude/skills/
├── req-uc-auth/SKILL.md requirements: authentication cases
├── req-personas/SKILL.md requirements: personas
├── req-business-rules-core/SKILL.md requirements: business rules
├── ba-acceptance-criteria/SKILL.md analysis: how to write criteria
├── qa-bug-report/SKILL.md testing: how to file findings
├── dev-backend-new-endpoint/SKILL.md development: new endpoint procedure
├── dev-tests-backend-unit/SKILL.md development: unit tests
└── dev-workflow-git/SKILL.md development: merge conflicts

A new role joins the team and a new prefix appears, not a new wiki.

What a skill looks like. Frontmatter with a name and a description, then ordinary markdown:

.claude/skills/dev-backend-new-endpoint/SKILL.md
---
name: dev-backend-new-endpoint
description: Procedure for adding an HTTP endpoint: contract, validation,
layers, tests, client regeneration. Use when creating a new endpoint
or backend module.
---
# A new endpoint
1. Describe the contract in `api.md` — before the code, or the two diverge.
2. The controller only receives and returns; logic in the service, data
access in the repository.
3. Input validation is mandatory, errors in the shared format.
4. Tests: the happy path, a validation failure, missing permissions.
5. Regenerate the frontend client and check that it builds.
## Checklist
- [ ] api.md updated before the code
- [ ] no logic in the controller
- [ ] all three tests in place
- [ ] client regenerated, build green

The description is the most important field: the agent decides from it whether the skill fits the task. "A FastAPI reference" works poorly. "FastAPI architecture and production practices; use when building or reviewing FastAPI services" works well.

It is essentially a small routing prompt: it has to carry both the subject and the trigger condition. The skill's body, meanwhile, is kept short, because once loaded it stays in the context until the end of the turn and is paid for by every following message. The same considerations apply here as in context engineering: a long reference is better split, with the detail moved into neighbouring files the skill points at.

Requirements as skills. A requirements skill looks like a normal analyst's document. Use cases with their own ids, personas, boundaries, acceptance criteria. Nothing "for the agent" about it:

.claude/skills/req-uc-auth/SKILL.md (excerpt)
## UC-AUTH-002 Sign in with email and password
**Personas:** PER-001 Agent, PER-002 Manager
**Priority:** P0
### Acceptance criteria
- AC-UC-AUTH-002-001 — with valid credentials a session opens and works
in both applications without signing in again.
- AC-UC-AUTH-002-002 — with a wrong password the answer is identical
whether or not the account exists (no enumeration hints).
- AC-UC-AUTH-002-003 — after five consecutive failures sign-in for that
email is blocked for 15 minutes.

And here comes the main part: tasks and bugs reference those ids. A tracker entry carries a link rather than a retelling. The requirement's text is copied nowhere and lives in one place. If an entry and a requirement disagree, the requirement document wins.

It cures a familiar disease: a criterion rewritten into a task, into the wiki and into a test case, and three versions of it six months later.

But this is only one of the options

Keeping documentation as skills is my choice, not the one right path. People solve this very differently, and it's worth knowing what the options are.

A plain documentation folder in the repository. The simplest variant: markdown in docs/ plus a line in the project instructions saying where it lives. Nothing to configure, humans read it in GitHub as is. There is exactly one downside, and it is substantial: the agent won't remember it exists until you name a file. It doesn't know what's inside and at best will find something by text search.

AGENTS.md. A cross-tool context file read by Codex, Cursor and a dozen other agents. Claude Code reads its own instructions file but can import AGENTS.md, so the two coexist. A good choice for a team where different people use different agents. The limitation is the same as for project instructions: it is one file and must not swell into a reference manual.

An external wiki over MCP. Confluence, Notion, Linear, whatever: the documentation stays where people already maintain it, and the agent reaches it through an MCP server. The analyst edits a page in the familiar interface, with comments and history, and the agent reads the same thing. You pay with the network, accounts, and the fact that the documentation's version lives apart from the code's: the page changed yesterday while the branch you are fixing matches last month's state.

Obsidian and its kin. A local vault of markdown files that the agent reaches either through an MCP server or by simply being given the directory. The format is the same plain text, so the vault can live in git, and both a human with their link graph and search and the agent work on top of it. People use this as shared memory between tools and sessions: notes, decisions and drafts in one place available to Claude Code and to another agent alike. For a personal knowledge base it is an excellent option. For a team's project documentation it is weaker: the link to the code is lost again, and the vault lives a life of its own.

Documentation platforms with MCP and llms.txt. Modern services like GitBook or Mintlify serve documentation in two shapes at once: a human site and a machine-readable layer, down to an automatically provisioned MCP server. Sensible when the documentation is public and has to be published somewhere anyway.

Search over a large corpus. With thousands of pages of documentation none of the options above fits into the context, and the question moves into retrieval: a vector store, hybrid search, a dedicated server handing the agent the right fragments. That is engineering in its own right, not "put the files in a folder".

What skills win

One property, and it is why I moved to them: the agent always knows they exist. The list of skills with their descriptions enters the context at the start of every session, so the agent pulls the right document itself, unprompted. A documentation folder doesn't work that way: until you name a file, it might as well not be there.

The rest is smaller but pleasant. Documentation is versioned together with the code, so an old branch holds the documentation matching that old code. Edits go through normal pull request review. Nothing needs configuring, everything works offline, without tokens or external services. And the format is being standardized: the same skill can be handed to more than just Claude Code.

What you pay with

The downsides are better listed up front.

The skill list itself occupies context. Not the bodies but the roster of names and descriptions, and it does so every session. At a hundred skills that is already hundreds of lines you pay for always. The description even has a length limit past which it gets truncated: the developers are clearly defending against that list growing unchecked.

It is awkward for humans to read. Inside a repository there is no documentation search, no table of contents, no margin comments, no comfortable handling of images. An analyst used to Confluence will not go into GitHub for requirements. In my case that is solved by the requirements being edited by the same person who works with the agent, but in a large team it becomes a problem.

No collaborative editing or discussion. A comment on a paragraph, a discussion inside the document, a "you were mentioned" notification — none of that exists. There is a pull request, which is far more formal.

Several repositories mean several copies. Company-wide practices then have to be duplicated, or linked with symlinks, or packaged into a plugin. In a monorepo there is no problem; across five repositories it is noticeable.

The temptation to dump everything in. Skills are cheap, so it is easy to throw a hundred files in and end up with the same dead wiki, only in git. The one safeguard is the description the agent picks a skill by. Written carelessly, the document will never be pulled in, and you won't even find out.

No role filtering. A developer sees the analyst's skills and vice versa, and everyone pays for them in context. More on that below, in the section about roles.

If it comes down to one rule: what the agent needs while working on the code is better kept next to the code; what people need for discussion and planning is better kept where people are used to working. My boundary runs roughly like this: requirements and practices as skills, while plans, deadlines and correspondence stay in the external system.

What it costs

Project instructions and rules load into every session in full. In my case that is 571 lines of instructions plus 1245 lines of rules. Almost two thousand lines before you have typed your first word.

Noticeably more than recommended, and I pay for it in context. A deliberate trade: predictability in a project with several applications and shared infrastructure is worth more than the saved tokens. But the trade has a limit. If the file kept growing, I would scatter the rules across path patterns.

By the way, if the agent keeps violating some rule, the cause is almost always volume rather than wording. The rule got lost. Cured by shortening, not by adding capital letters.

Documentation: how and where

The principle is simple. All documentation the agent reads lives in rules and skills. There is no "docs for the agent" folder at all, so that there are never two sources of truth. That is one project's decision, not the only possible one: I went through the alternatives and their price above.

What follows from it:

Rules and skills are the source of truth. The agent is explicitly forbidden from inventing behaviour that contradicts them. If it thinks a document is wrong, it says so to a human rather than acting on its own.

Don't edit the documentation unasked. It seems odd at first and turns out to be necessary: an agent that trips over an inconvenient rule will happily bring the rule into line with its own behaviour.

Something new gets a new document; a change to something existing edits the existing one. Otherwise in six months the project holds two skills about the same thing with different versions of the truth.

One template. There is a dedicated skill about how to write rules and skills: structure, phrasing, delimiters, a mandatory checklist at the end. The checklist turns a description into a verifiable requirement and gives the agent a way to check whether it followed the document.

That skill is essentially a body of prompt-engineering techniques applied to a project's configuration, and the rule requires using it whenever any rule or skill is created or edited. It sounds bureaucratic and it pays off: documents written to one template contradict each other less often and are followed better. The techniques themselves are covered in the article on prompt engineering.

README for humans, instructions for the agent. Both are needed, but the readers differ. The README explains to a newcomer what goes on here; the agent relies on instructions, rules and skills. When the project structure changes, both are updated, or the developer and the agent end up living in different pictures of the repository.

Technical references separately. Deployment, migrations and load test results live in an ordinary documentation folder at the root. The agent doesn't need them while working on code; a human needs them occasionally.

A task and bug tracker right in the repository

The most idiosyncratic part of my setup.

Tasks and bugs live in the repository as plain markdown files, one file per entry. No database, no web interface, no CI checks. The script does two things: create an entry and rebuild the index. Automation ends there.

management/
├── tasks/
│ ├── auth/TASK-001-one-password-for-both-apps.md
│ ├── billing/TASK-037-invoice-pdf-export.md
│ ├── done/TASK-012-import-legacy-contacts.md
│ ├── INDEX-AUGUST-2026.md
│ └── TEMPLATE.md
├── bugs/
│ ├── auth/BUG-003-sign-in-loops-on-expired-token.md
│ ├── done/BUG-001-avatar-upload-fails-over-2mb.md
│ └── INDEX-AUGUST-2026.md
├── decisions/ADR-0001-where-user-sessions-are-stored.md
├── logs/roman/2026-08-06/a1b2c3d4.log
└── new.py

What this gives you. The agent sees the whole task without leaving the project, together with the requirement it references and the code it edits; the entry lives in the same history as the code, so it is always visible which commit closed it and what changed along the way. Two people (or two agents) working on different entries physically never edit the same file, so merge conflicts cannot happen by construction — probably the strongest argument against the one big task-list file everyone is used to. Plus everything works offline, without accounts or tokens.

What an entry looks like

Here is the real bug template, only with invented content:

management/bugs/auth/BUG-003-sign-in-loops-on-expired-token.md
---
id: BUG-003
title: 'Sign-in loops on an expired token'
severity: P1
status: CONFIRMED
module: auth
area: 'sign-in / refresh token'
found: 2026-08-04
closed:
assignee: ''
req: 'AC-UC-AUTH-002-001'
source: 'QA wave 12'
---
# BUG-003 — Sign-in loops on an expired token
- **Steps:** open the app with a token older than 24h → sign-in screen →
enter valid credentials → back to the sign-in screen.
- **Expected:** AC-UC-AUTH-002-001 — a valid sign-in opens a session.
- **Actual:** the refresh call returns 401 and the client retries forever.
- **Log:**
- 2026-08-04 — filed from the QA wave.
- 2026-08-05 — reproduced on staging, confirmed. Refresh interceptor
does not clear the stale token before retrying.
## Screenshots
- 2026-08-04 — `BUG-003_04-AUGUST-2026_11-20_loop.png` — the loop, three
redirects in the network tab.

An entry is created by a command, not by copying a file by hand:

Terminal window
python management/new.py bug --module=auth --severity=P1 \
--title="Sign-in loops on an expired token"
python management/new.py task --module=auth --priority=P1 \
--title="One password for both apps"
python management/new.py attach BUG-003 shot.png --note="the loop after the fix"

The script allocates the next free number, puts the file into the module folder and rebuilds the month's index. The index looks like this:

management/tasks/INDEX-AUGUST-2026.md
# Task index — AUGUST 2026
**Entries:** 31 · **Open:** 30 · **Closed:** 1
| ID | Prio | Status | Batch | Module | Title |
| --- | --- | --- | --- | --- | --- |
| TASK-029 | P1 | IN_PROGRESS | 2026-08-04-export | billing | Export invoices as PDF from the admin panel |
| TASK-030 | P1 | TODO | PROJ-42 | platform | Choose a feature-flag library |
| TASK-031 | P1 | TODO | PROJ-42 | platform | Set up CI/CD for the staging environment |

Details that matter in practice:

Ids are permanent. They are never reused and never renumbered: commits, correspondence and other entries reference them.

Folders are named after requirements. The area an entry belongs to is named after the requirements file that owns it: req-uc-auth → the auth folder. Tasks and bugs share the same folders. A small thing, but it removes the eternal "where does this go" argument and connects the tracker with the documentation.

Closing only through git mv. The status changes, a date is set, a line is appended to the log, the file moves into the closed folder. A plain move shows up in history as a delete plus a new file, and the entry's whole history is lost. I didn't learn that from the documentation.

Screenshots in the repository. A separate command puts the image into the attachments folder, names it by a scheme and appends a line to the entry. Images only, up to four megabytes, cropped: git keeps every byte forever. Video goes in as a link. The screenshots section is append-only, because a bug that lived for three weeks carries a shot per state, and the old lines have to show how it was then.

The language is English in Latin script. The file name is built from the title, and the tracker is read by people with no shared mother tongue. A description may stay in the language the report arrived in if asked, but the title and the metadata are always Latin.

What never goes into the tracker

The most important rule of the whole scheme, and it goes against habit: the tracker holds the product and nothing else.

There is exactly one test. Does it run at the customer's side? The applications, the libraries they are built from, the data, the infrastructure — yes, that is the product.

Everything that exists only to make building the product easier for us doesn't enter the tracker at all. Not as a task, not as a bug, regardless of how much work it was or who asked. The repository's documentation, rules, skills, the tracker itself and its script, hooks, service scripts, CI configuration, moving folders around. That work is simply done, and the record is the commit plus the automatic session log.

The wording in the rule is harsh, deliberately so:

No entry at all — however broken it looks and however much work
it cost: a stale document, a dead link, a wrong path in a rule,
a defect in this very tracker or in a service script, a hook misreading
its own input, a broken CI setting, a renamed folder, a rule
somebody asked for. None of it reaches the customer,
so none of it is a bug. And turning it into a task is the same mistake
under a different label. Don't file it. Do the work, the commit speaks for it.

The temptation is strong: there is a lot of work and you want it visible. But a tracker stuffed with housekeeping is one nobody reads, and reading is exactly what it was created for.

The second split is tasks versus bugs. Anything QA finds is a bug, and only a bug. Fixing a bug does not create a task. A task is new functionality, or rework of existing functionality, that somebody asked for.

Decisions

Next to the tasks live architectural decisions, one file per decision. The format is the familiar ADR one:

management/decisions/ADR-0001-session-storage.md (structure)
---
id: ADR-0001
title: Where user sessions are stored: database or cache
status: Proposed
date: 2026-07-31
deciders: Engineering lead / Architect
related:
- management/tasks/auth/TASK-001, TASK-002
- Requirements BR-SEC-004 (one session shared by both apps)
supersedes: none
---
## Context what forced the decision and which question is blocked
## Decision numbered points: what exactly was decided
## Verified facts (checked in code, not assumed) a "fact → where in code" table
## Consequences what this makes easier and what we pay for it
## Alternatives what was considered and why it was rejected

The verified-facts section came about after one decision was made on somebody's retelling and the code turned out to say otherwise. Now every fact in an ADR carries a reference to the place in the code it came from.

The point is simple: an argument settled once doesn't resurface every two months.

The session log that writes itself

The third part of the tracking, and nobody maintains it by hand. A hook records everything that happened in the session: your prompts, the agent's questions and what you picked, per-turn statistics, changed files with line counts, the final report. A file per session, a folder per day, a folder per developer.

It looks like this:

management/logs/roman/2026-08-06/a1b2c3d4.log
session start: source=startup · model=claude-opus-5
════════════════════════════════════════════════════════════
prompt:
fix the infinite redirect on an expired token, BUG-003
turn: model=claude-opus-5 · effort=high · 4m 12s · tools=23
(Read 9, Grep 5, Edit 3, Bash 6) · subagents=1
files:
~ apps/web/src/api/interceptors.ts +14/-6 +[41-48,52-57] -[41-46]
~ apps/web/tests/auth.spec.ts +31/-0 +[88-118]
answer:
The cause was the interceptor retrying the request with the stale token...
subagent done: general-purpose
session end: reason=prompt_input_exit · duration=51m

Why, when the agent is right there? Because asking it what happened in the session is a bad idea. By the end of long work its context is already compacted: some details are gone, others recounted optimistically. A mechanical record made by something other than the model is the only honest answer to "what actually happened".

Forty-seven such files piled up over the course of the project. I opened them more often than expected: usually to work out when and why a strange piece of code appeared. It is also the answer to "so where is the work on the repository itself tracked, the work that must never become a task". Right here. Automatically, without a single line of manual bookkeeping.

What the scheme doesn't have

Honestly about the limits. There is no board, no notifications, no sprint reports, no time tracking. If management lives in Jira, this scheme doesn't replace it: in my case the grouping label holds a Jira key.

The division comes out like this. In the external system, planning and reporting for a manager. In the repository, what the executor needs while working, together with the code and the requirements.

What's missing: roles

Since I've grouped skills by prefix for different teams, let me also say what the tool itself lacks for that scheme.

The prefix in the name is a convention, not a mechanism. The analyst, the tester and the developer work in one repository and get one and the same configuration: identical instructions, the full set of skills, identical write permissions.

Three annoyances, all three of which I've seen for real. The analyst asks for a spec-versus-code comparison, and the agent, loaded with developer instructions, starts editing the sources. The tester asks to reproduce a defect, the shared instructions say "apply the migrations before testing", and the agent dutifully applies them to a shared environment. And, quieter but dearer: everybody pays context for every skill, including the ones their work never touches.

The workaround is obvious and poor: everyone keeps a personal configuration in their home folder. It isn't in the repository, nobody reviews it, and a newcomer simply doesn't have it.

The right answer is to make a role a full configuration layer that lives in the repository. Its own share of the instructions, its own set of skills and agents, its own permissions and, crucially, its own writable scope that edits cannot leave:

.claude/
├── CLAUDE.md the shared part for everyone
├── settings.json
├── skills/
└── roles/
├── dev/{CLAUDE.md, settings.json, skills/, agents/}
├── qa/{CLAUDE.md, settings.json} no migrations, no deploys
└── ba/{CLAUDE.md, settings.json} writes only to docs/ and specs/

And it has to be a prohibition at the tool level, not a request in the text. Exactly the same difference as between a rule and a hook.

I filed this as a feature request for roles in Claude Code: with the problem described, the merge semantics for settings (a role can only narrow the project's permissions, never widen them), ways to select a role and a minimal useful version. If the pain is familiar, go and support it — the more confirmations, the better the chance somebody gets to it.

Subagents

A subagent is a separate session with its own context, its own tools and its own system prompt. It does part of the work and returns the result.

The main benefit isn't speed but a clean context. An agent finding its way around unfamiliar code will read dozens of files, and all of that settles in your session. A subagent reads them in its own window and returns the conclusions.

A subagent of your own is described in one file:

.claude/agents/security-reviewer.md
---
name: security-reviewer
description: Reviews changes for vulnerabilities. Run before a release
and on edits to authentication, file uploads or database access.
tools: Read, Grep, Glob, Bash
model: opus
---
You are a senior security engineer. Review the changes for:
- injections (SQL, XSS, shell commands);
- authentication and authorization flaws;
- secrets in the code;
- unsafe handling of user data.
Point at specific lines and suggest a fix. Leave style alone.

The body of that file is another model's system prompt, not a note to self. Everything applies as usual: an assigned role, explicit boundaries ("leave style alone"), an output format and a trigger condition in the description. On writing prompts for agents and tool use there is a separate section in the prompt article.

The second use is independent review. Whoever wrote the code is a poor reviewer: they see their intentions rather than the diff. It is worth asking a subagent specifically:

Use a subagent to review the changes against PLAN.md. Check that every
requirement is implemented, that the listed edge cases have tests and
that nothing outside the task changed. Report gaps, not style preferences.

A caveat worth knowing in advance: a reviewer asked to find shortcomings will find them. Even when the work is sound, because that is what it was asked for. Chasing every remark leads straight to extra abstraction layers and tests for impossible cases.

And about money. Subagents are never cheaper: each carries its own context and its own scaffolding. A fan-out of ten costs roughly as much as ten sessions. An edit in one or two files is always faster in the main session; delegating makes sense for reading many files.

A separate story is several parallel sessions. The easiest way is to separate them with git worktrees: each gets its own directory and its own branch, and edits don't collide. The "one writes, another reviews" scheme works for the same reason as the reviewer subagent: a fresh context isn't infected by the author's reasoning.

Context in practice

Techniques I use constantly:

A new task means a clean context. Clearing between unrelated tasks is cheaper than any optimization. The tail of somebody else's task is both extra tokens and extra distractions.

Two failed attempts mean stop. Corrected twice and it's still wrong? The problem is no longer the wording: the context is stuffed with failed approaches. A clean session with a more precise brief nearly always beats a long one with accumulated corrections.

Compaction is configurable. When the window runs out, the history is compacted automatically. You can ask for compaction with an emphasis, and state in the project instructions what must survive:

CLAUDE.md
When compacting, always preserve: the list of changed files,
the test commands, and the architectural decisions with their reasons.

Rewind instead of caution. Every prompt of yours is a restore point; both the conversation and the files can be brought back. That changes how you work: instead of agonizing over a risky step, try it and roll back. Just remember that rewind is not git: changes made through the command line aren't tracked.

A quick question on the side. For trivia like "what does this flag do" there is a mode where the answer never enters the history.

The status line. How full the window is deserves to stay in front of your eyes. Otherwise you notice the problem at the moment compaction has already happened.

Permissions

By default the agent asks permission for everything that changes the system. Safe and unbearable: by the tenth confirmation you aren't reading, you're clicking.

There are three ways to cut that down, of varying radicalism. A list of pre-approved commands:

.claude/settings.json
{
"permissions": {
"allow": [
"Bash(npm run test:*)",
"Bash(npm run lint)",
"Bash(git status)",
"Bash(git diff:*)",
"Read(src/**)"
],
"deny": [
"Read(.env)",
"Read(.secrets/**)",
"Bash(git push:*)"
]
}
}

Then auto mode, where a separate classifier lets routine through and stops on anything suspicious. And a sandbox with filesystem and network isolation: the strictest option, where the agent can be given more freedom precisely because the system holds the boundaries.

Whatever you choose, one rule stands. Read the full diff before a commit. It mattered before agents and matters more now: the model happily edits things nobody asked about and tidies up a neighbouring file while it's there.

A useful detail about hooks: a prohibition from a hook is stronger than any permission mode, it fires even where confirmations are switched off entirely. So whatever must not be touched under any circumstances is closed off by a hook, not by a request.

Models, effort and money

An agent's work consists of steps of wildly different difficulty. Understand the task, find the files, apply an edit, check the result. There is no point keeping the most expensive model on every step.

The practice is this: the main session on a strong model, mechanical work delegated to subagents on a simpler one, the effort level raised only where there is a hard fork. Both skills and subagents can set the model and effort for themselves, so the routing can be described once:

.claude/skills/dev-changelog/SKILL.md
---
name: dev-changelog
description: Build a changelog from the commit history between two tags.
model: haiku
effort: low
disable-model-invocation: true
---

Subscription limits are a topic of their own. The agent doesn't see them, so it cannot size its spending: it fans out subagents just as happily when the reset is four hours away as when the budget is nearly gone. In my case that is solved by a hook that brings live numbers into the context and forbids fan-outs in a hot zone. Details in the hooks article.

MCP and command-line tools

Two ways to give the agent access to the outside world.

Command-line tools are the most context-efficient path. If a service has a CLI, the agent uses it perfectly well: files tasks, reads comments, looks at logs, brings up an environment. It picks up unfamiliar utilities too, if you ask it to work them out from the built-in help.

MCP servers are for structured access: a database, a tracker, designs, monitoring. The same category holds servers for working with the code itself: symbol navigation instead of reading whole files saves a great deal on large repositories.

A tip from practice: connect only what you actually use. Every server adds descriptions of its tools to every session's context, and a dozen servers kept "just in case" costs more than it seems.

What stayed true from the Cursor practices

Some advice from the old article hasn't gone anywhere. It is about working with a model in general.

Decompose and control the steps. A "build it all" request produces code that doesn't work the way you expected, and untangling it costs more than writing it would have. Steps must be self-sufficient: after each one the project builds.

Log generously. A console overloaded with logs hinders a human and helps the agent: matching output against code, it pins down what broke far more precisely.

Ask for current library versions and watch the licences. The model will insert the version it remembers, and it remembers one from two or three years ago. It doesn't look at licences at all, and dragging a copyleft library into a closed project is easy.

Settle on the approach in advance. One thing can be done in five ways, and without a decision the model picks — based on what appeared most often in its training data. Ask about the options and their downsides before the code is written.

Different tasks, different sessions. The advice about a new chat per task carried over unchanged, only now it has a precise explanation: it is about context.

How to ruin it all

I've collected the ways I see most often. Including in my own work.

The junk session. Started with one task, asked about another, went back to the first. The context is stuffed with everything at once.

Endless corrections. The agent did the wrong thing, you corrected it, still wrong. After two rounds it is cheaper to start over.

Bloated instructions. The file grew and half of its directions stopped working: the important part got lost. Cured by ruthless trimming.

Trust without verification. A plausible implementation that doesn't cover the edge cases. Nothing to check it with — don't ship it.

Endless exploration. "Work out how everything here fits together" with no boundaries, and the agent has read two hundred files while the window ran out. Scope the investigation or hand it to a subagent.

In short

If you take one thing from this article: set up the environment first, then ask for code.

A check the agent can run itself. Short project instructions holding only what it can't derive from the code. Rules for mandatory behaviour, skills for knowledge on demand, hooks for what must happen every time. Task tracking next to the code. A deliberate attitude to context.

The work is one-off and pays off every session. And the difference between "the agent constantly does the wrong thing" and "the agent does what's needed" usually lives here, not in the model and not in the phrasing of the latest request.

And once more about where I started: all of this is prompts. Instructions, rules, skills, subagent descriptions. Written carelessly, they give exactly the result careless prompts give, except you pay for it every session rather than once. The techniques that work here are covered in the article on prompt engineering.

On hooks — the mechanism that makes the agent's behaviour deterministic — there is a separate article with every event and three working examples.