Choose CLAUDE.md, Hooks, or Skills for AI-Assisted Development

    This guide is for engineers who already use coding assistants such as Claude Code. It explains when to use CLAUDE.md, Hooks, or Skills, then shows how to package a repeatable release-notes workflow as a Skill with metadata, scripts, and acceptance checks.

    Who this is for and what you need first

    This article is for engineers who already use AI coding assistants such as Claude Code in day-to-day development. Before you follow it, you need a Git repository, the ability to run shell scripts in a terminal, and a clear understanding of your project’s test commands and code conventions.

    The directory layout, file names, and fields here match Claude Code versions that support Skills. Field names can change between versions, so check them against the official documentation for the version you use. These settings directly change how the AI behaves. Try them first on a personal branch or in a test repository, and merge them into the team’s main line only after they work as expected.

    Step 1: When to use CLAUDE.md, Hooks, or Skills

    The three mechanisms solve different problems. Choose by two criteria: when the instruction takes effect, and whether the AI can skip it.

    Start with this decision tree:

    1. [I want the AI to remember or do something]
    2. │
    3. ▼
    4. Is this needed in every session?
    5. │
    6. ├─ Yes → CLAUDE.md (loaded automatically when a session starts)
    7. │
    8. └─ No → Must it run whenever a specific event happens?
    9. │
    10. ├─ Yes → Hooks (run by the client, not by the AI’s judgment)
    11. │
    12. └─ No → Is it a fixed multi-step workflow?
    13. │
    14. ├─ Yes → Skills (loaded when the task matches)
    15. │
    16. └─ No → Is it low-frequency reference material?
    17. │
    18. ├─ Yes → Put it in project docs and note the path in CLAUDE.md
    19. │
    20. └─ No → Do not configure it yet; package it after it repeats

    After the decision tree, compare the three options:

    Dimension CLAUDE.md Hooks Skills
    When it applies Loaded when a new session starts When a specified event occurs (for example, before or after a tool call, or when a session ends) When the task matches the Skill description
    How strict it is Advisory; the AI may ignore it or misread it Deterministic; a script can block later actions by returning a specific exit code Depends on the model following the steps; the workflow itself is not enforced
    Maintenance cost Low, but harder to maintain as it grows Medium; scripts need review and testing Medium to high; metadata, scripts, and examples all need upkeep
    Typical use Project background, stack, directory conventions, naming rules Pre-commit checks, blocking edits to protected files Multi-step tasks such as release notes, dependency upgrades, and troubleshooting

    Two points matter. First, CLAUDE.md is guidance, not a constraint. The AI can still drift from rules written there during a complex task, so security or compliance requirements belong in Hooks. Second, do not put every piece of low-frequency knowledge into CLAUDE.md. It consumes context in every session. Keep infrequent material in separate documents and point to those paths from CLAUDE.md.

    Step 2: Write a Skill that actually runs

    A Skill is useful when it freezes a repeated multi-step task. The example below generates release notes from commits between two Git tags, including the full directory and file contents.

    Directory layout:

    1. .claude/skills/release-notes/
    2. ├── SKILL.md # Metadata and step instructions
    3. ├── scripts/
    4. │ ├── collect_commits.sh # Collect commit history
    5. │ └── validate_output.py # Check output format
    6. └── examples/
    7. ├── input.md # Sample input
    8. └── expected_output.md # Expected output for acceptance

    SKILL.md is below. The metadata at the top of the file decides whether the AI can find and load the Skill in the right situation, so the description must state when to use it:

    1. ---
    2. name: release-notes
    3. description: Generate release notes from commits between two Git tags. Use when the user asks for release notes, a changelog, or an update log.
    4. ---
    5. # Generate release notes
    6. ## Inputs
    7. - from_tag: start tag, required, for example v1.2.0
    8. - to_tag: end tag, required, for example v1.3.0
    9. If either parameter is missing, ask the user. Do not guess.
    10. ## Steps
    11. 1. From the project root, run `bash .claude/skills/release-notes/scripts/collect_commits.sh <from_tag> <to_tag>` to get the commit list.
    12. 2. Group commits by message prefix: feat under “New features,” fix under “Bug fixes,” refactor under “Internal refactors,” and everything else under “Other.”
    13. 3. Keep the commit hash and a one-sentence description for each entry.
    14. 4. Run `python3 .claude/skills/release-notes/scripts/validate_output.py <output file>` to check the format.
    15. ## Success criteria
    16. - The number of generated entries matches the number of commits from the collection script.
    17. - Every entry includes a commit hash.
    18. - The validation script returns 0.
    19. ## Failure handling
    20. - Step 1 fails because a tag does not exist: stop and report the missing tag name.
    21. - Step 2 finds commits that cannot be classified: put them under “Other” and list those commit hashes in the report.
    22. - Step 4 fails: fix the format and retry once. If it still fails, keep the draft, do not overwrite the official file, and report the first error line from the validator.

    The commit-collection script is below. Tag names come from user input, so the script quotes every variable and checks that each tag exists before use, so unexpected input is not concatenated into a command:

    1. #!/usr/bin/env bash
    2. set -euo pipefail
    3. from_tag="${1:?missing start tag}"
    4. to_tag="${2:?missing end tag}"
    5. for tag in "$from_tag" "$to_tag"; do
    6. if ! git rev-parse --verify --quiet