Module 2: Automated Code Review on PRs
Customizing Your Team's Conventions
Customizing Your Team's Conventions
Overview
So far your bot works with a generic prompt: "do a code review of this diff". The result is generic comments: "consider type hints", "this name could be clearer", "add a docstring". Useful, but not specific to your project.
Every team has nuances: particular naming conventions, accepted patterns that differ from the standard, legacy modules with different rules, documented architectural decisions. A bot that applies generic rules loses value fast — the team starts to perceive it as noise.
This capsule teaches you to feed the bot your team's specific conventions via CLAUDE.md and customized prompts. By the end, you'll have a bot that applies the rules your team defined, not the ones the model assumes generically.
The Problem with Generic Conventions
GENERIC BOT:
> "The name createOrder should be create_order to
follow snake_case in Python."
THE TEAM:
> "Yes, we know. But that file is legacy from when
we used camelCase. We don't want to refactor all
of it now. Ignore the comment."
RESULT:
- Technically correct comment
- Pragmatically useless comment
- Team disables the bot because "it comments on things that
don't matter"
The bot needs to know that that module is an exception. That information lives in the team's head — but now you can pass it to the bot.
CLAUDE.md as the Source of Truth
CLAUDE.md is the file where you document the project's conventions so Claude Code (and other AI agents) apply them. It was born for interactive sessions but it also works in CI.
Recommended structure
# Project Conventions
## Stack
- Python 3.11+, Flask, SQLAlchemy
- TypeScript on the frontend (React)
## Naming
- Python: snake_case for functions/variables, PascalCase for classes
- TypeScript: camelCase for variables, PascalCase for types/components
- Documented exceptions:
- `src/legacy/` keeps the original camelCase — don't refactor
- External APIs use camelCase for compatibility
## Accepted patterns
- Service layer mandatory for business logic (not in routes)
- Repositories via the SQLAlchemy ORM (no raw queries)
- Type hints on public functions (not on private ones to reduce noise)
## Forbidden patterns
- `bare except` (always specify the exception)
- `print()` in production code (use the logger)
- SQL string concatenation (always parameterized)
## Structure
- `src/api/routes/` — I/O only, no logic
- `src/services/` — business logic
- `src/models/` — SQLAlchemy models
- `src/utils/` — generic helpers
## Tests
- Pytest, fixtures in `tests/conftest.py`
- Minimum coverage: 70% on new modules
- Integration tests in `tests/integration/`
## Known tech debt (don't comment)
- `src/legacy/payments_v1.py` — to be deprecated in Q3
- Missing type hints in `src/utils/*` — explicit backlog
Loading CLAUDE.md into the bot's prompt
from pathlib import Path
# Load the conventions
claude_md_path = Path("CLAUDE.md")
if claude_md_path.exists():
conventions = claude_md_path.read_text()
else:
conventions = "No documented conventions."
# Build the prompt with the conventions inline
prompt = f"""You are a professional code reviewer for this project.
PROJECT CONVENTIONS (from CLAUDE.md):
{conventions}
INSTRUCTIONS:
- Apply THESE conventions, not generic conventions
- If an exception is documented, do NOT flag it as an issue
- If the tech debt is in the "don't comment" list, ignore it
- Your output must respect the structured JSON of the review
DIFF TO REVIEW:
{diff_text}
[rest of the prompt with the expected JSON format]
"""
Result: the bot understands that src/legacy/ uses camelCase on purpose, that print() is forbidden, that missing type hints in src/utils/* shouldn't be commented.
Conventions by File Type
Sometimes the rules depend on the file type. CLAUDE.md can be structured like this:
## Rules by Type
### Python (`*.py`)
- snake_case
- type hints on public functions
- docstrings on functions of >5 lines
### TypeScript (`*.ts`, `*.tsx`)
- camelCase for variables, PascalCase for types
- Strict mode enabled, no `any`
- Props with explicit interfaces
### YAML (`.github/workflows/*.yml`)
- Pin action versions (`@v4`, not `@latest`)
- Explicit permissions per job
- Comments on non-obvious steps
### SQL migrations (`migrations/*.sql`)
- Idempotent (IF NOT EXISTS, IF EXISTS)
- Documented down migration
- Destructive changes require explicit approval
The bot receives this information in the prompt and applies rules based on the file's extension.
Examples of Good and Bad Implementations
More effective than abstract rules: showing examples of acceptable code and rejectable code. CLAUDE.md can include:
## Examples: Error Handling
### ✅ ACCEPTABLE
\```python
try:
result = process_payment(amount)
except PaymentValidationError as e:
logger.warning(f"Validation failed: {e}")
raise PaymentError("Invalid input") from e
except ExternalAPIError as e:
logger.error(f"Stripe API failed: {e}")
raise PaymentError("Payment provider unavailable") from e
\```
### ❌ REJECTABLE
\```python
try:
result = process_payment(amount)
except Exception as e: # bare-ish, catches everything
logger.error(e) # no context
return None # silences the error
\```
**Why it matters:** the second example silences errors and makes debugging harder. The first has specific exceptions, context in the logs, and propagates with `from e`.
Examples speak louder than rules. The model uses them as pattern matching to detect violations.
Allowing Override by File or Directory
Some exceptions are per file. You can document them inline in the code with marked comments:
# claude-code: skip-review
def legacy_function(camelCaseParam):
# ... code we don't want to comment on
pass
The bot can ignore marked files or functions. Implementation in the script:
def filter_skipped_sections(diff_text: str) -> str:
"""Remove sections marked as skip-review from the diff."""
lines = diff_text.split("\n")
result = []
skip = False
for line in lines:
if "claude-code: skip-review" in line:
skip = True
elif skip and line.strip().startswith("def ") and not line.startswith("+"):
skip = False
if not skip or line.startswith("---") or line.startswith("+++"):
result.append(line)
return "\n".join(result)
Trade-off: flexibility vs accountability. If you abuse the marker, the bot becomes useless. Document the usage criteria in CLAUDE.md.
Per-Team Configuration in a Monorepo
In monorepos, different sub-projects can have different conventions. Hierarchical CLAUDE.md:
monorepo-project/
├── CLAUDE.md ← global conventions
├── apps/
│ ├── web/
│ │ └── CLAUDE.md ← override for the frontend
│ └── api/
│ └── CLAUDE.md ← override for the backend
└── packages/
└── shared/
└── CLAUDE.md ← conventions for the shared lib
Script to load the closest CLAUDE.md to each modified file:
def load_conventions_for_file(file_path: str) -> str:
"""Look for CLAUDE.md up the tree."""
path = Path(file_path).parent
convention_files = []
while path != path.parent:
candidate = path / "CLAUDE.md"
if candidate.exists():
convention_files.insert(0, candidate.read_text())
path = path.parent
# Add the root
if Path("CLAUDE.md").exists():
convention_files.insert(0, Path("CLAUDE.md").read_text())
# Combine (the more specific ones override)
return "\n\n---\n\n".join(convention_files)
For PRs that touch multiple areas of the monorepo, you can run the bot per area with the relevant CLAUDE.md.
Iterating the CLAUDE.md Based on the Bot's Comments
A practice that continuously improves the bot: when the bot comments something the team dismisses as a false positive, add the exception to CLAUDE.md.
Week 1 PR:
Bot: "createOrder doesn't follow snake_case"
Developer: "that file is intentional legacy"
Action: add to CLAUDE.md → "src/legacy/orders.py keeps camelCase"
Week 2 PR:
Bot: no longer comments the case
Developer: "the bot improved"
Week 3 PR:
Bot: "raising a generic Exception is forbidden"
Developer: "that try/except is to catch external SDK errors"
Action: add to CLAUDE.md → an example of an accepted pattern for
external SDK errors
Every false positive is information about how to refine the conventions. After 4-6 weeks of iteration, CLAUDE.md precisely describes the team's real rules.
Common Pitfalls
Error 1: A generic CLAUDE.md copied from the internet
Symptom: The "conventions" don't reflect what the team actually does.
Why it happens: Copying a template without real observation of the code.
How to fix it: Start by reading 5-10 files of the project. Document which patterns are actually used. The documented conventions should be observable in the code.
Error 2: A CLAUDE.md with no examples
Symptom: The bot interprets the rules differently from how the team applies them.
Why it happens: Abstract rules have ambiguity. "Type hints mandatory" — on all functions or only public ones? On parameters or only on the return?
How to fix it: Include examples of acceptable and rejectable code. They eliminate ambiguity.
Error 3: The CLAUDE.md isn't updated
Symptom: The bot keeps commenting things the team dismissed months ago.
Why it happens: "I'll update CLAUDE.md later" — it doesn't happen.
How to fix it: Establish a rhythm: every time the bot comments a false positive, add it to CLAUDE.md in the same adjustment PR. It keeps the file alive.
Error 4: Too many exceptions
Symptom: CLAUDE.md has 30 files marked as exceptions. The bot basically comments on nothing.
Why it happens: The team prefers to disable rules rather than apply them.
How to fix it: If 50% of the project is an exception to a rule, the rule is wrong. Reformulate it. Maybe snake_case isn't the real convention — it's something more nuanced.
Error 5: A CLAUDE.md with no priorities
Symptom: All the rules have the same weight. The bot comments on critical and trivial things with the same severity.
Why it happens: A flat structure of rules.
How to fix it: Categorize by severity. "Critical rules (always comment)" vs "Suggestions (only if impactful)". The prompt uses that categorization to decide severity.
Diagnosis
Question 1: Is your CLAUDE.md based on real observation or on a template?
If it's a template, the conventions probably don't reflect the real code. Audit by reading 5-10 files.
Question 2: Does your CLAUDE.md include examples of acceptable and rejectable code?
Without examples, the rules are ambiguous. Examples eliminate interpretation.
Question 3: When was the last time you updated CLAUDE.md?
If it's "months ago", it's out of date. A living CLAUDE.md is the most useful.
Question 4: Do you document known tech debt so the bot doesn't comment on it?
If not, the bot will comment on debt the team already knows about and decided to postpone. It creates noise.
Question 5: Do the CLAUDE.md rules have priorities/severity?
If they all have the same weight, all the bot's comments sound equally important. Categorizing lets the bot prioritize well.
Exercises
Exercise 1: Create a minimal CLAUDE.md based on observation (Medium)
For your project:
- Read 5 representative files
- Document the 3-5 most visible conventions
- Add 2 examples (acceptable + rejectable) per convention
- Identify 2-3 areas of known tech debt you don't want the bot to comment on
See starter template
# Project Conventions
## Stack
- [Language + version]
- [Main framework]
## Naming
- [General convention]
- Exceptions:
- [File/dir and why]
## Accepted patterns (with example)
### [Category 1]
✅ ACCEPTABLE: [code]
❌ REJECTABLE: [code]
**Why:** [explanation]
## Known tech debt (don't comment)
- [File]: [reason]
Exercise 2: Load CLAUDE.md into the prompt (Easy)
Modify the review script so it loads CLAUDE.md and includes it in the prompt before asking for the review.
Exercise 3: Iterate over false positives (Hard)
For 2 weeks, note down each of the bot's false positives. At the end:
- Categorize the false positives
- For each category, add a specific exception to CLAUDE.md
- Verify that the following week the bot no longer comments on those categories
Summary
- CLAUDE.md is the source of truth for the team's conventions
- Base it on observation, not on generic templates
- Examples > abstract rules — they eliminate ambiguity
- Documenting known tech debt avoids comments on assumed debt
- Iterating the CLAUDE.md based on false positives makes it more useful over time
- A hierarchical CLAUDE.md in monorepos allows rules per area
- Categorizing by severity helps the bot prioritize comments
Next capsule: 06 — Handling large PRs with chunking. Your bot works perfectly on small and medium PRs. What happens when an 80-file PR arrives? The module's last capsule covers the extreme case: chunking, prioritization, and when to say "this PR is too large for automatic review".
Additional Resources
- Anthropic: CLAUDE.md best practices — Official documentation
- Google Style Guides — Examples of documented conventions
- Airbnb JavaScript Style Guide — Popular conventions as a reference
- PEP 8 — Python's default conventions
- Effective Python — Brett Slatkin — Idiomatic patterns for CLAUDE.md
- Ruff configuration — How other linters codify conventions (conceptual reference)