Keeping Claude Code on track
TL;DR
When Claude does something weird, ask where it got it from: usually from your own code. When it misbehaves, tell it why, so it remembers. Where you can, enforce the rule with a tool, so it can’t break it. Keep conversations short.
How it works
Claude decides how to write code in your project from three things:
- Your code. It reads the code around the change and copies the patterns it finds.
- Its memory. Notes that are loaded at the start of every conversation: the ones Claude writes itself when you correct it, and a
CLAUDE.mdyou can write yourself. How Claude Code remembers goes into the details. - The conversation. Everything said, read and run so far: its context.
Keeping Claude on track means keeping those three clean.
My take
- Ask where it got it from, instead of telling it what it did wrong. If Claude does something weird, you probably did it first, somewhere. “Where did you get this from?” points you at the code it copied. Now you’ve found something to clean up in your code base, and once it’s fixed there, no future conversation copies it again.
- Tell it when it misbehaves, and why. “Don’t do X, because Y.” It saves that to its memory, so the next conversation doesn’t make the same mistake. The “because” lets Claude apply the rule to cases you didn’t mention.
- Enforce your rules with tools, as you would for colleagues. Checkstyle, a formatter,
.editorconfig, ArchUnit tests for what may depend on what. Claude runs the build, sees what fails, and fixes it. A rule in its memory is a suggestion it can forget or apply wrongly. A failing build is a hard constraint: it can’t make up its own version of a rule it isn’t allowed to break. - Keep conversations short. Start a new one for every task.
/contextshows how full the current one is. Above about 120k tokens, have Claude write what matters to a Markdown file, and start a new conversation from that file. Tokens, context and usage limits explains why.
Gotchas
- Quality drops long before the context window is full. Claude forgets earlier instructions and mixes in attempts you already rejected.
- Fixing its output by hand teaches it nothing. The next conversation makes the same mistake.
- Leftovers from the previous task steer the next one. Files, decisions and rejected ideas from an unrelated task are still in the context.
Example
You: Why did you use field injection with
@Autowiredhere? Where did you get that from?Claude: I followed
OrderServiceandInvoiceService, which both inject their dependencies that way.
Fix those two classes (or let Claude do it), then: “Use constructor injection, because it makes dependencies explicit and the class testable without Spring. Remember that.”
Claude saves that as a memory, and every later conversation in this project follows it. How Claude Code remembers shows what that looks like.
Better still, make it fail the build, with an ArchUnit test:
@AnalyzeClasses(packages = "com.example")
class CodingRulesTest {
@ArchTest
static final ArchRule noFieldInjection = GeneralCodingRules.NO_CLASSES_SHOULD_USE_FIELD_INJECTION;
}
Now field injection can’t come back, from Claude or from a colleague.