Skip to main content

Compositions

CLAUDE.md instructions are advisory — Claude can deprioritize or ignore them. Hooks are deterministic — they execute regardless of what Claude decides. The entire composition framework rests on this distinction: CLAUDE.md for guidance, hooks for guarantees.

CLAUDE.md + Hooks

Hooks transform CLAUDE.md from "please follow these rules" into an enforcement layer with real consequences. The two systems complement each other at every stage of a session.

Re-inject Context After Compaction

Compaction strips subdirectory CLAUDE.md files and skill descriptions from context. A SessionStart hook with a compact matcher re-injects critical instructions that would otherwise disappear:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Reminder: use pnpm, not npm. Run tests before committing. Current sprint: auth refactor.'"
          }
        ]
      }
    ]
  }
}

You can also embed compaction instructions directly in CLAUDE.md:

When compacting, always preserve the full list of modified files and any test commands.

Both approaches work. The hook is more reliable because it executes deterministically rather than depending on Claude reading and following the instruction during compaction.

The InstructionsLoaded Hook

The InstructionsLoaded hook fires when any CLAUDE.md or .claude/rules/*.md file loads into context. Matchers filter by load reason:

MatcherWhen It Fires
session_startInitial session load
nested_traversalLazy-loaded subdirectory CLAUDE.md
path_glob_matchPath-scoped rule triggered by file access
include@import expansion
compactRe-injection after compaction

Example — log which instruction files load and when:

{
  "hooks": {
    "InstructionsLoaded": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{file: .file_path, reason: .reason}' >> ~/.claude/instruction-loads.log"
          }
        ]
      }
    ]
  }
}

This log reveals exactly which instructions are active at any point in a session — invaluable for debugging why Claude followed one rule but ignored another.

Enforcing What CLAUDE.md Cannot

CLAUDE.md says "never edit migration files." Claude might still do it. A PreToolUse hook guarantees it:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

The hook script examines the file path in tool_input, checks it against protected patterns, and exits with code 2 to block the operation. The reason printed to stderr becomes Claude's feedback, so it adjusts its approach.

A minimal protection script:

#!/bin/bash
FILE_PATH=$(jq -r '.tool_input.file_path' <<< "$CLAUDE_TOOL_INPUT")
 
if [[ "$FILE_PATH" == *"/migrations/"* ]]; then
  echo "BLOCKED: Migration files are immutable. Create a new migration instead." >&2
  exit 2
fi

Auto-Format After Edits

Instead of wasting CLAUDE.md lines on formatting rules (semicolons, quotes, indentation), use a PostToolUse hook:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null; true"
          }
        ]
      }
    ]
  }
}

This removes 5-10 lines of formatting instructions from your CLAUDE.md while achieving better compliance. Linters are deterministic; LLM-based formatting is not.

CLAUDE.md + Skills

Skills are markdown files in .claude/skills/ that teach Claude repeatable workflows. They load on demand rather than every session — the key distinction from CLAUDE.md.

The Decision Boundary

Put it in CLAUDE.mdMake it a skill
Applies to every sessionOnly matters for specific workflows
Short (1-3 lines)Multi-step procedure
Build commands, conventionsDeployment process, code review checklist
Architecture overviewDetailed API design guide

Skills with disable-model-invocation: true in their frontmatter stay completely out of context until explicitly invoked with /name. Zero token cost until needed.

CLAUDE.md as a Skill Directory

Reference skills from CLAUDE.md so Claude knows they exist without loading their full content:

# Workflows
- For deploying, use /deploy (handles staging + production)
- For code review, use /review-component
- For new API endpoints, use /new-endpoint

This costs 3 lines of CLAUDE.md (~30 tokens) instead of embedding entire workflow procedures that could cost 500+ tokens each.

Migrating Bloated CLAUDE.md to Skills

A common evolution path for projects where CLAUDE.md has grown past 200 lines:

  1. Identify multi-step procedures in your CLAUDE.md (deployment, onboarding, code review)
  2. Extract each into .claude/skills/<name>.md
  3. Replace the CLAUDE.md section with a one-line reference
  4. Add disable-model-invocation: true if it should only fire on explicit /name invocation

Before:

## Deployment Process
1. Run the full test suite with `pnpm test`
2. Build production bundle with `pnpm build`
3. Check bundle size with `pnpm analyze`
4. If bundle exceeds 100KB, investigate with source-map-explorer
5. Deploy to staging with `pnpm deploy:staging`
6. Run smoke tests against staging
7. Deploy to production with `pnpm deploy:prod`
8. Verify production health checks pass

After (in CLAUDE.md):

- For deploying, use /deploy

After (in .claude/skills/deploy.md):

---
name: deploy
description: Full deployment pipeline from test to production
disable-model-invocation: true
---
[Full deployment procedure here]

CLAUDE.md + Auto Memory

Two complementary systems, both loaded at session start but serving different purposes.

CLAUDE.mdAuto Memory
Who writes itYouClaude
What it containsInstructions and rulesLearnings and patterns
Size limitNo hard limit (but under 200 lines recommended)First 200 lines or 25KB of MEMORY.md
Survives compactionProject root: yes. Subdirectory: reloads lazilyRe-read from disk on demand
Version controlledYes (project-level)No (stored in ~/.claude/projects/)

Auto memory stores in ~/.claude/projects/<project>/memory/MEMORY.md plus topic files. All worktrees and subdirectories within the same git repo share one auto memory directory.

When to Use Which

If you find yourself correcting Claude repeatedly in chat — "no, we use pnpm" — that correction should be in CLAUDE.md. If Claude discovers a project-specific pattern through work — "the auth middleware expects a Bearer token in the X-Custom-Auth header" — auto memory captures it automatically.

The overlap zone: when Claude keeps making the same mistake and auto memory doesn't capture the correction, add it to CLAUDE.md manually. The instruction is too important to leave to automatic discovery.

CLAUDE.md + Subagents

Subagents receive their own copy of CLAUDE.md, counted against the subagent's context window, not yours. This has specific implications for instruction architecture.

What Subagents Inherit

  • Project-root CLAUDE.md: loaded fresh
  • Ancestor CLAUDE.md files: loaded fresh
  • CLAUDE.local.md: loaded fresh

What Subagents Do NOT Inherit

  • Parent conversation's auto memory
  • Skills from the parent (must be listed explicitly in skills frontmatter)
  • Conversation context from the parent session
  • The ability to spawn their own subagents (prevents recursion)

Built-in Explore and Plan agents skip CLAUDE.md loading entirely for a smaller context footprint.

Preloading Skills Into Subagents

---
name: api-developer
description: Implement API endpoints following team conventions
skills:
  - api-conventions
  - error-handling-patterns
---

This gives the subagent access to specific skills without loading them into the parent session's context.

Writing Instructions for Subagents

Because subagents get their own copy of CLAUDE.md, instructions that reference "the current conversation" or "what we discussed earlier" will not work. Write instructions as self-contained directives:

# CLAUDE.md — subagent-safe instructions
 
## API Conventions
- All endpoints return `{ data, error, meta }` response envelope
- Use Zod schemas for request validation (see src/schemas/)
- Error responses follow RFC 7807 Problem Details format
 
## Test Requirements
- Every new endpoint needs integration tests in __tests__/
- Run `pnpm test:api` to verify (not the full suite)

CLAUDE.md + MCP

MCP tool names load as deferred references at startup (~120 tokens). Full schemas load on demand when Claude uses tool search. CLAUDE.md can guide MCP tool selection:

# MCP Tools
- Use the Playwright MCP for UI testing (available via browser-tester subagent)
- Prefer `gh` CLI over GitHub MCP for simple operations (lower token cost)
- The Notion MCP is configured for the engineering wiki workspace

Keeping MCP Overhead Low

If your MCP servers define more than 20K tokens of tool definitions, performance degrades measurably. Two strategies:

  1. Use ENABLE_TOOL_SEARCH=auto to defer schema loading until Claude needs a specific tool:
ENABLE_TOOL_SEARCH=auto claude
  1. Define MCP servers inline in a subagent's mcpServers field rather than in .mcp.json — keeps them out of the main session entirely:
---
name: ui-tester
description: Run UI tests with Playwright MCP
mcpServers:
  playwright:
    command: npx
    args: ["@anthropic/mcp-playwright"]
---
 
Run Playwright tests against the staging URL. Navigate to each page,
verify interactive elements work, and report failures with screenshots.

The Full Stack

A production setup combining all systems:

project/
├── CLAUDE.md                    # 50-100 lines: build, architecture, conventions
├── CLAUDE.local.md              # 10-20 lines: personal focus, local env
├── .claude/
│   ├── settings.json            # Hooks for enforcement and automation
│   ├── rules/
│   │   ├── code-style.md        # Unconditional style rules
│   │   └── api-safety.md        # Path-scoped for src/api/**
│   ├── skills/
│   │   ├── deploy.md            # /deploy workflow
│   │   ├── review.md            # /review workflow
│   │   └── new-endpoint.md      # /new-endpoint scaffold
│   └── hooks/
│       ├── protect-files.sh     # Block edits to migrations
│       └── auto-format.sh       # Prettier after every edit

The settings.json tying this together:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/protect-files.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/auto-format.sh"
          }
        ]
      }
    ]
  },
  "permissions": {
    "allow": [
      "Bash(pnpm test:*)",
      "Bash(pnpm build)",
      "Bash(pnpm typecheck)"
    ]
  }
}

CLAUDE.md provides the mental model. Hooks enforce the boundaries. Skills hold the procedures. Auto memory captures the learnings. Each system handles what it does best — no overlap, no gaps.