CLAUDE.md = global, always-loaded constraints
Things that apply regardless of what file Claude is touching. "Never modify the database schema" is the right example — it doesn't matter whether Claude is editing a service class, a controller, or a utility — that rule is always in force. It belongs in CLAUDE.md because it needs to be present in every session from the first prompt.
.claude/rules/*.md = scoped, path-specific guidance
Rules files only load when Claude is working on files that match the paths frontmatter. The SQL transaction example:
---
paths:
- "src/db/**/*.sql"
---A practical split for a typical enterprise Java project might look like:
CLAUDE.md— never touch legacy module, always write tests, use the internal artifact registry.claude/rules/persistence.mdscoped tosrc/db/**— transaction boundaries, naming conventions for stored procs.claude/rules/api.mdscoped tosrc/main/java/**/controller/**— REST conventions, response wrapper standards, no business logic in controllers
Each rules file stays small and precise. The context window only carries what's relevant to the current work.
Hooks: running your own scripts at fixed points in the lifecycle
bypassPermissions mode — which removes every other safety net — does not suppress hooks. Your PreToolUse hook still fires. This means hooks are the one enforcement mechanism that sits above the permission mode system, making them the right place for constraints you genuinely cannot afford to miss.The /hooks command
Rather than hand-editing the JSON, you can run /hooks inside a Claude Code session and it gives you an interactive UI to configure them, which then writes the JSON for you. That's the intended workflow — configure via /hooks, inspect the result in settings.json.
Example:
check-prod-guard.js — blocks writes to prod config:
const filePath = input.path || '';
if (filePath.includes('application-prod.properties')) {
process.stderr.write('BLOCKED: Production config cannot be modified by agent. Edit manually.');
process.exit(2); // exit code 2 = block the tool call
}
process.exit(0); // exit code 0 = allow it throughcompile-check.sh — runs after any Java edit:
EDITED_FILE="$1"
# Only react to Java file edits
if [[ "$EDITED_FILE" == *.java ]]; then
echo "Running compile check after edit to $EDITED_FILE..."
mvn compile -q 2>&1 | tail -20
fiYour settings.json with both hooks wired in
"syntaxHighlightingDisabled": true,
"autoUpdatesChannel": "latest",
"theme": "light",
"permissions": {
"allow": ["Read", "Glob", "Grep"]
},
"enabledMcpjsonServers": ["java-tools"],
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node C:/Users/ravi.kumar.garlapati/.claude/hooks/check-prod-guard.js '$CLAUDE_TOOL_INPUT'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash C:/Users/ravi.kumar.garlapati/.claude/hooks/compile-check.sh '$CLAUDE_TOOL_INPUT_PATH'"
}
]
}
]
}
}What happens at runtime
Scenario: Claude decides to edit src/main/resources/application-prod.properties
Claude attempts: Edit(application-prod.properties)
→ PreToolUse fires
→ check-prod-guard.js runs
→ detects "application-prod.properties" in path
→ exits with code 2
→ stderr: "BLOCKED: Production config cannot be modified by agent"
→ tool call never executes
→ Claude sees the reason and tries a different approachScenario: Claude edits src/main/java/com/accenture/OrderService.java
Claude attempts: Edit(OrderService.java)
→ PreToolUse fires → path is safe → exits 0 → edit proceeds
→ Edit executes, file is modified
→ PostToolUse fires
→ compile-check.sh runs mvn compile
→ if compile fails, output is shown to Claude
→ Claude sees the error and fixes it in the next turn
The key difference from a CLAUDE.md instruction you can see clearly here: even if Claude somehow decided to edit application-prod.properties despite a CLAUDE.md rule saying not to, the PreToolUse hook intercepts it at the tool call level before execution. The model's decision never reaches the filesystem.
Subagents: delegating work to an isolated context
Who triggers a subagent?
The main Claude session triggers it automatically based on the complexity and nature of your prompt. You don't explicitly say "spawn a subagent now." Claude Code decides internally whether to delegate a task to a subagent or handle it inline in the main session.
For example if you say:
"Explore the legacy payment module and tell me what classes depend on PaymentGateway"
Claude Code may decide that's an exploration task, spawn the Explore subagent to do the codebase investigation, get the result back, and present it to you — all transparently. You just see the answer.
You can also explicitly invoke one by naming it in your prompt:
"Use the general-purpose subagent to refactor OrderService following our project conventions"
Do you configure it in settings.json or a .md file?
Neither, for built-in subagents — they require no configuration at all. They're part of Claude Code itself.
For custom subagents you create them as .md files inside .claude/agents/ in your project:
your-project/
.claude/
agents/
java-refactor.md ← your custom subagent
api-reviewer.md ← another custom subagent
settings.json
CLAUDE.mdEach file has a frontmatter header that defines what it loads:
---
name: java-refactor
description: Refactors Java classes following Accenture project conventions
skills:
- java-conventions
- spring-boot-patterns
paths:
- "src/main/java/**/*.java"
---
You are a Java refactoring specialist.
Always use Spring Data JPA, never native Hibernate queries.
Always wrap service methods with @Transactional.
Never modify files outside src/main/java.When does it trigger?
| Trigger type | How |
|---|---|
| Automatic | Claude Code decides based on task type — exploration, investigation, parallelizable work |
| Explicit prompt | You name the subagent or task type in your message |
| Custom subagent | Claude matches your request to the description field in the agent's frontmatter and picks the right one |
The description field in your custom subagent's frontmatter is essentially how Claude Code decides which agent to route a task to — so write it precisely, the same way you'd write a method's Javadoc to describe exactly what it does and when to call it.
No comments:
Post a Comment