AIDive

Video-Paket

Skill, Hook, Subagent oder MCP: die 4 Dateien und die Entscheidungskarte

7 Min. Lesezeit

TL;DR und Fazit

Vier Jobs, die du ständig in Claude Code tippst, gehören jeweils in eine andere Datei. Eine Release-Checkliste ist ein Skill. Eine Regel, die nie brechen darf, ist ein PreToolUse-Hook. Ein Audit, das viele Dateien liest, ist ein Subagent. Daten, die Claude nicht sehen kann, sind ein MCP-Server.

Wir haben alle vier an einem echten Repo ausgeführt, einer Rechnungs-API mit 34 Tests, und jedes Mal zuerst die falsche Wahl getroffen. Die falschen Wahlen scheiterten auf dem Bildschirm:

  • Die Checkliste in CLAUDE.md fügte /context bei jedem Turn eine Zeile Memory files: 210 tokens hinzu.
  • Der erste Hook lief, gab seine Warnung aus, und der Schreibzugriff ging durch (unitPrice von 8000 auf 12000).
  • Das Audit inline flutete den Thread: Messages bei 19.3k Tokens, gegenüber 7.2k mit einem benannten Subagent.
  • Ohne Docs-Server antwortete Claude "I couldn't find that setting... I won't guess one."

Fazit: Wähle die Art nach der Docs-Tabelle und sorge dann dafür, dass die Datei antwortet, nicht nur existiert. Ein Hook, der nur loggt, ist unverbindlich, wie die CLAUDE.md-Zeile, die er ersetzt hat. Von den vieren fragt nur der Hook nie nach.

Kopiere die Dateien unten, committe sie und beginne mit der, die du am häufigsten neu tippst.

Quellenbasierte Synthese

Was deine Session vor deinem ersten Wort lädt. Unser Baseline-/context zeigte 26.2k/1m Tokens, ohne Memory-Zeile und ohne MCP-Zeile. Jeder Job, den du hinzufügst, setzt entweder eine Zeile in diese Tabelle oder nicht. Lies sie zuerst und miss dann daran.

Eine CLAUDE.md-Regel ist eine Bitte, ein Hook ist Durchsetzung. Die Docs sagen, eine Anweisung wie "never edit .env" in CLAUDE.md oder in einem Skill sei eine Bitte, und ein PreToolUse-Hook, der die Änderung blockiert, sei Durchsetzung s1. Die Memory-Seite nennt CLAUDE.md Kontext, keine Durchsetzung s8. Ein Thread auf Hacker News berichtete dasselbe von der Hook-Seite: Ein Hook, der mit 0 endet, blockiert nichts s10.

Die Trigger-Tabelle sagt dir, welche Datei. Das dritte Einfügen einer Prozedur heißt Skill. Ausgaben, die den Chat fluten, heißen Subagent. Ein Tab, den Claude nicht sehen kann, heißt MCP-Server. Etwas, das jedes Mal passieren muss, heißt Hook s1.

Job 1, die Release-Checkliste, ist ein Skill.

  • Eine Prozedur, die in CLAUDE.md gewachsen ist, gehört in einen Skill, und sein Inhalt wird nur geladen, wenn er genutzt wird s3.
  • In unserem Lauf fügte die in CLAUDE.md eingefügte Checkliste eine Zeile Memory files: 210 tokens hinzu. Als Skill mit disable-model-invocation: true ging /context zurück auf die Baseline.
  • /release führte sechs Schritte aus (34 Tests, 0.4.0 auf 0.4.1, Tag v0.4.1). Es bemerkte auch, dass git commit -am nicht verfolgte Dateien überspringt, und stagte deshalb zuerst den neuen Changelog.

Job 2, die Regel, die gelten muss, ist ein Hook, und die Garantie ist die Antwort, die er zurückgibt.

  • Exit 0 ohne Ausgabe ist keine Entscheidung. Exit 2 blockiert. Das Deny-JSON blockiert ebenfalls s2.
  • Unser erster Hook loggte, endete mit 0, und der Schreibzugriff ging durch (unitPrice von 8000 auf 12000). Claudes Antwort erwähnte den Hook nie.
  • Derselbe Hook, der das Deny-JSON zurückgab, lehnte die Änderung ab mit PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). Danach fragte Claude: übergehen oder die Migration schreiben.
  • Die Docs veröffentlichen ein PreToolUse-Rezept für geschützte Dateien s23.
  • Die Grenze: Dieser Hook beobachtet Edit und Write. Decke auch die Shell ab, oder halte die Shell von deiner Allow-Liste fern.
  • onFailure: "block" lässt einen fehlgeschlagenen Hook blockieren, statt offen zu scheitern. Es kam in v2.1.295 s2 s15.

Job 3, das Audit, das flutet, ist ein Subagent.

  • Ein Subagent startet blind und gibt eine Zusammenfassung zurück s4.
  • Inline zeigte Messages 19.3k. Mit dem benannten Subagent zeigte es 7.2k.
  • Die Agent-Datei allein delegierte in 0 von 2 Läufen. Ihn im Prompt zu nennen, delegierte. Die Datei lässt ihn existieren, dein Prompt lässt Claude ihn nutzen.
  • Lege model: in der Datei fest. Die Rechnung bewegte sich nicht: Das Lesen findet weiterhin statt, nur nicht in deinem Fenster.

Job 4, Daten, die Claude nicht erreicht, sind ein MCP-Server.

  • claude mcp add --scope project schreibt .mcp.json, und die Tool-Suche lädt nur Tool-Namen s5.
  • Es gibt einen kostenlosen HTTP-Server ohne Login für die eigenen Docs von Claude Code, mit search_claude_code_docs und query_docs_filesystem_claude_code_docs s24.
  • Ohne ihn antwortete Claude "I couldn't find that setting... I won't guess one." Mit ihm antwortete Claude "onFailure": "block", eingeführt in v2.1.295, während unsere Installation 2.1.294 war.
  • Ein Docs-Server für eine Bibliothek, die das Modell schon kennt, änderte bei uns nichts. Verbinde, was das Modell nicht haben kann.

Erfahrungsberichte. Ein Reddit-Thread berichtete, Claude ignoriere eine "MUST use agent"-Zeile in CLAUDE.md s9. Wir zitieren ihn als Bericht, nicht als Messung. Die Engineering-Beiträge von Anthropic behandeln dieselbe Aufteilung s6 s7.

Code

Vier Dateien aus dem Lauf, plus zwei Helfer. Die Pfade zeigen, wo jede in deinem Repo liegt.

1. Der Release-Skill, unter .claude/skills/release/SKILL.md. Tippe /release. disable-model-invocation: true hält ihn für dich zum Starten, was du für etwas willst, das committet und taggt.

SKILL.md

---
name: release
description: Cut a patch release of ledgerly (typecheck, tests, version bump, changelog line, commit, tag).
disable-model-invocation: true
---

Run these steps in order and stop at the first failure:

1. `pnpm typecheck` must exit 0.
2. `pnpm test` must pass.
3. Bump the patch version in `package.json` (e.g. 0.4.0 -> 0.4.1).
4. Add one line at the top of `CHANGELOG.md` (create it if missing): `## vX.Y.Z - YYYY-MM-DD` followed by a one-line summary of the commits since the last tag.
5. `git commit -am "release vX.Y.Z"`
6. `git tag vX.Y.Z`. Never push.

SKILL.md on GitHub

2. Der Hook zum Schutz der Daten, in .claude/settings.json. Er gibt die Deny-Entscheidung mit einem Grund zurück, den Claude liest. Er braucht jq im PATH und passt inline, sodass der Hook keine eigene Skriptdatei braucht. Ändere das Muster data/.*\.json$ und den Grund auf deine Regel.

settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
          }
        ]
      }
    ]
  }
}

settings.json on GitHub

3. Der Conventions-Auditor, unter .claude/agents/conventions-auditor.md. Die Tools sind auf schreibgeschützte beschränkt, und das Modell ist festgelegt. Passe die Liste der Konventionen an dein README an.

conventions-auditor.md

---
name: conventions-auditor
description: Use when asked to audit the code against the README conventions
model: sonnet
tools: Read, Grep, Glob
---

You audit ledgerly's source against README § Conventions:

- Money is always an integer number of cents. Never floats.
- Domain functions return new objects; they never mutate their input.
- Validation failures throw `ValidationError` (400), missing records `NotFoundError` (404), illegal state transitions `ConflictError` (409).
- A change of stored shape bumps `schemaVersion` and adds a migration in `src/store/migrations.ts`.

Read every file under `src/`. Return only the findings: one line per violation as `file:line - convention - what is wrong`, then borderline cases the same way, then one line with the count of files read. No file contents, no summary of clean files.

conventions-auditor.md on GitHub

4. Die MCP-Konfiguration, gespeichert als .mcp.json im Repo-Root. Sie ist das, was claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp schreibt. Claude Code listet einen Projekt-Server als ausstehend, bis du ihn in deiner nächsten Session freigibst.

mcp.json

{
  "mcpServers": {
    "claude-docs": {
      "type": "http",
      "url": "https://code.claude.com/docs/mcp"
    }
  }
}

mcp.json on GitHub

Helfer. ctx.sh führt /context in einer Headless-Session aus und gibt die Token-Zeilen aus, damit du vorher und nachher vergleichen kannst. mcp-probe.sh sendet initialize und tools/list an eine HTTP-MCP-URL, um zu prüfen, ob ein Server antwortet, bevor du ihn hinzufügst.

ctx.sh

#!/usr/bin/env bash
# Usage: ctx.sh <step>  -> runs "/context" through run-session.sh, writes context-<step>.txt (honours STRICT_MCP / EXTRA_ALLOW)
C="$(cd "$(dirname "$0")" && pwd)"
"$C/run-session.sh" "$C/out/context-$1.json" "/context" >/dev/null
python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(d['result'])" "$C/out/context-$1.json" > "$C/context-$1.txt"
grep -E '^\*\*Tokens|^\| (System prompt|System tools|MCP tools|Memory files|Skills|Custom agents|Messages) ' "$C/context-$1.txt"

ctx.sh on GitHub

mcp-probe.sh

#!/usr/bin/env bash
# Usage: mcp-probe.sh <url> [tools/call json params]  -> initialize + notifications/initialized + tools/list (+ optional tools/call) over streamable HTTP
U="$1"; H=$(mktemp)
curl -s -D "$H" -m 30 -X POST "$U" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
echo; SID=$(grep -i '^mcp-session-id:' "$H" | tr -d '\r' | cut -d' ' -f2); echo "session-id: ${SID:-none}"
HS=(-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream'); [ -n "$SID" ] && HS+=(-H "Mcp-Session-Id: $SID")
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; echo
[ -n "${2:-}" ] && { curl -s -m 90 -X POST "$U" "${HS[@]}" -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":$2}"; echo; }
rm -f "$H"

mcp-probe.sh on GitHub

Das machst du am Montag

  • Öffne eine frische Session und führe /context aus. Notiere die Summe. Das ist deine Baseline.
  • Nimm die Checkliste, die du am häufigsten einfügst. Verschiebe sie nach .claude/skills/<name>/SKILL.md, füge disable-model-invocation: true hinzu, wenn sie committet oder taggt, und führe sie mit /<name> aus.
  • Nimm die eine Regel, die nie brechen darf, und lege sie in einen PreToolUse-Hook, der permissionDecision: "deny" zurückgibt oder mit 2 endet. Bitte Claude, die Regel zu brechen, und prüfe, ob die Änderung abgelehnt wird.
  • Wenn dein Hook nur loggt, ändere ihn. Ein Hook, der mit 0 endet, blockiert nichts.
  • Verschiebe eine Aufgabe, die viele Dateien liest, nach .claude/agents/<name>.md mit festgelegtem model:. Nenne ihn im Prompt, bis Claude ihn selbst wählt.
  • Prüfe einen Fakt, den Claude dir zuletzt geraten hat. Wenn er neuer ist als das Modell, füge einen Docs-Server mit claude mcp add --transport http <name> --scope project <url> hinzu und committe .mcp.json.
  • Prüfe deine Claude-Code-Version, bevor du dich auf eine Einstellung verlässt. onFailure: "block" braucht 2.1.295.
  • Führe git add -A && git status aus und committe die neuen Dateien. Unser Lauf zeigte genau vier.

Weiterführend

Die Entscheidungskarte. Eine Seite. Das sind die Zeilen der Docs s1 mit unseren vier Jobs darauf. Das Herzstück dieses Pakets sind die vier Dateien oben plus diese Karte.

Art Was sie tut Nutze sie, wenn Unser Job Unsere Datei
CLAUDE.md Dauerhafter Kontext, in jeder Unterhaltung geladen Eine Konvention, die Claude zweimal falsch macht Eine Zeile, keine Prozedur keine
Skill Anweisungen und Abläufe, die Claude nutzen kann Du fügst dasselbe Playbook zum dritten Mal ein Die Release-Checkliste .claude/skills/release/SKILL.md
Hook Ein durch Ereignisse ausgelöstes Skript Es soll jedes Mal passieren, ohne nachzufragen Gespeicherte Daten nie von Hand bearbeiten .claude/settings.json
Subagent Isolierter Context, der zusammengefasste Ergebnisse zurückgibt Eine Nebenaufgabe flutet deine Unterhaltung Das Conventions-Audit .claude/agents/conventions-auditor.md
MCP Verbindung zu externen Diensten Du kopierst ständig Daten aus einem Tab, den Claude nicht sehen kann Die Docs von Claude Code .mcp.json
Plugin Bündelt die vier zu einer installierbaren Einheit Ein zweites Repository braucht dasselbe Setup Nicht genutzt keine

Die Karte entscheidet die Art, nicht die Qualität. Ein Hook, der nur loggt, ein Subagent, den du nie nennst, und ein Server für Docs, die das Modell kennt, saßen jeweils in der richtigen Art und scheiterten. Wähle die Art, und sorge dann dafür, dass die Datei antwortet.

Wann du nicht zu diesen greifst.

  • Eine Konvention, die Claude immer wieder falsch macht, bleibt in CLAUDE.md als eine Zeile, nicht als Prozedur.
  • Ein Job, den du hin und her steuerst, mit deinem Verlauf im Blick, bleibt im Haupt-Thread, nicht in einem Subagent.
  • Ein Docs-Server für eine Bibliothek, die das Modell schon kennt, kostet Context und ändert nichts.
  • pnpm test ist ein CLI, das du schon hast. Es ist kein MCP-Job.

Plugins. Ein Plugin ist die Kiste, die die vier zusammen ausliefert. Greife zu einem, wenn ein anderes Repo dasselbe Setup braucht.

Skill plus MCP. Die Docs koppeln sie: Der Server liefert die Verbindung, und der Skill bringt Claude bei, sie gut zu nutzen.

Skill mit context: fork. Ein Skill kann in einem geforkten Subagent laufen, sodass sein Lesen aus deinem Fenster bleibt. Teste es an einem Job, bevor du dich darauf verlässt.

Hook-Typen jenseits von command. Command ist der, den wir genutzt haben. Die Hooks-Referenz listet weitere Handler-Typen, darunter http- und prompt-Hooks. Dieselbe Regel gilt: Was der Hook zurückgibt, entscheidet, und ein stiller Hook entscheidet nichts.

MCP gegen CLI. Es gibt eine offene Debatte darüber, dass MCP-Server Context fressen, mit einem Beitrag, der erst das CLI empfiehlt s11, und einem weiteren über die Context-Kosten von MCP-Servern s12. Unsere Regel: Wenn ein CLI die Aufgabe schon erledigt, nimm es. Nutze MCP für das, was keines hat. Die Tool-Suche lädt jetzt nur Namen, also miss mit /context in deinem eigenen Setup, bevor du entscheidest s5.

Halte Skills kurz und überprüft. Ein kurzer Skill pro Job, gegen einen echten Lauf geprüft. Siehe das SkillsBench-Paper s13 und die Sicht eines Praktikers, warum Skills wenig kosten, bis sie genutzt werden s14.

FAQ

Warum lief mein Hook, aber die Änderung ging trotzdem durch? Ein Hook entscheidet nur durch das, was sein Befehl zurückgibt. Exit 0 ohne Ausgabe ist keine Entscheidung s2. Gib das Deny-JSON zurück, oder beende mit 2.

Warum liest Claude die Dateien selbst, obwohl ich einen Subagent habe? In unserem Lauf delegierte die Agent-Datei allein in 0 von 2 Läufen. Nenne ihn im Prompt, zum Beispiel "use the conventions auditor subagent".

Deckt der Hook alles ab, was Claude tun kann? Nein. Unserer trifft Edit und Write. Claude bot in einem späteren Lauf an, die Änderung über die Shell zu machen, also decke die Shell ab oder halte sie von deiner Allow-Liste fern.

Sollte ich für jede Bibliothek, die ich nutze, einen MCP-Server hinzufügen? Nein. Wenn das Modell die Bibliothek schon kennt, ändert ein Server nichts und kostet Context. Füge einen für Daten hinzu, die neuer sind als das Modell oder außerhalb deiner Platte liegen, und prüfe deine installierte Version gegen die, die das Feature eingeführt hat.

Kann ich das alles committen? Ja. Der Skill, die Settings-Datei, die Agent-Datei und .mcp.json liegen alle im Repo, sodass jeder, der es klont, sie bekommt. Gib den Projekt-MCP-Server einmal in deiner nächsten Session frei.

Dateien