Teaching Claude my conventions through 10 PR comments
When I first started using Claude on my homelab repo, the code it produced was correct but felt off. The naming was slightly different from the rest of the file. The function signatures used Optional[X] when the rest of the file used X | None. The error messages were longer than the rest of the file. The tests used a different pattern than the existing tests. The code worked. It didn’t match.
The first fix I tried was a long CLAUDE.md that documented every convention. That worked for a session. The next session, the agent drifted again. The CLAUDE.md was 800 lines and the agent wasn’t reading it carefully.
The fix that actually worked was simpler. I let the agent write code, then I gave it specific feedback in PR comments. After about 10 comments, the agent started writing code that matched the codebase on the first try.
Here’s what those comments looked like.
Comment 1: the type hint
In this file we use
X | None, notOptional[X]. The rest of the file is already in that style. Match the existing style.
Comment 2: the function signature
This function should be
parse_line(line: str) -> LogEntry | None. The convention in the rest of the file is to returnNonefor the not-found case, not to raise. Match the convention.
Comment 3: the error message
The error message is too long. The convention is “what went wrong, in 5-10 words, no apology.” See
load_config()for an example. Match the length.
Comment 4: the test pattern
Use the
given_when_thenpattern. Look attest_parse_line_handles_empty_stringintest_parser.pyfor the format. Match the structure.
Comment 5: the import
Don’t use
from typing import .... We’re on Python 3.12. Usefrom collections.abc import ...instead. The rest of the file already does this.
Comment 6: the docstring
One-line docstring only for simple functions. Multi-line only for the public API.
parse_lineis internal. Match.
Comment 7: the variable name
Use
resultnotret_val. The rest of the file usesresult. Match.
Comment 8: the constant
Move the magic number
0.7to a module-level constant with a name. The convention in this file is to name the constants. Look atSTABILITY_THRESHOLD_SECONDSin the same file. Match.
Comment 9: the helper
This helper is duplicated. There’s a
format_timestamp()helper inutils.py. Use it. Don’t re-implement.
Comment 10: the test name
Test name should describe the behavior, not the function.
test_parse_line_returns_none_for_blank_inputnottest_parse_line_blank. Match the existing test names.
After 10 comments like these, the agent started writing code that matched the conventions on the first try. The CLAUDE.md didn’t need to be 800 lines. The feedback loop did the work.
The pattern
The pattern is: the agent writes a draft. I review the diff. For each deviation from the codebase convention, I leave a short comment that says what the convention is and where the agent can see an example. The agent reads the comment. The next draft has fewer deviations.
The comments are not “this is wrong.” The comments are “here’s the convention and here’s where to see it.” The agent is smart enough to learn from examples. The agent is bad at inferring conventions from a 2000-line codebase without being told where to look.
The compounding effect
Each session the agent gets a little better at matching the codebase. The comments accumulate. The agent’s “model” of the codebase’s conventions improves. After 30 or 40 comments, the agent’s first drafts are usually 90% right. The remaining 10% are usually the subtle things (naming in a specific module, the order of arguments in a function call).
The compounding is the part that matters. The first session is slow. The tenth session is fast. The fiftieth session is barely any review at all. The agent has learned the codebase.
The thing this doesn’t replace
This doesn’t replace code review. The agent’s first drafts match the conventions but they don’t always match the intent. The conventions are surface. The intent is deeper. A PR comment about “use the helper” is a surface comment. A PR comment about “this helper isn’t right for this case because X” is an intent comment. The agent learns from both. The agent is better at learning from the surface comments.
For the intent comments, I still need a human reviewer. The agent can suggest the right code. The agent can’t tell whether the code matches what the system is supposed to do. That requires knowing the system. The agent knows the code. The agent doesn’t know the system.