Ein gutes CLAUDE.md ist kein README — es hält Invarianten fest, die Claude aus dem Code nicht ableiten kann. 6 zu schreiben, 4 zu vermeiden, 5 Fragen.
Mein Pickful-Projekt hat ein dezentrales Community-Jury-System, x402-Krypto-Zahlungen, Sign-In with Ethereum, Multi-Database-Setup, Echtzeit-Push — lauter Stacks, die erst in den letzten ein, zwei Jahren aufgetaucht sind. Claude liefert diese Features schnell und sauber.
Aber öffne das CLAUDE.md des Projekts, und du wirst feststellen: Jury-System und x402 — nicht ein einziges Mal erwähnt.
Das ist kein Versehen. Der Zweck von CLAUDE.md war nie, "Features zu beschreiben". Es geht darum, das festzuhalten, was Claude durch das Lesen des Codes niemals ableiten kann.
Wer zum ersten Mal ein CLAUDE.md schreibt, behandelt es oft wie ein README — jede Kernfunktion wird beschrieben:
Solche Inhalte liest Claude präziser aus topic_review_service.rb / x402.rb / like_points_service.rb, als du sie je schreiben wirst. Tausend Worte Business-Logik-Beschreibung kosten Claude einige hundert Tokens beim Lesen vom Code — ohne Interpretationsdrift. Code ist Fakt. Beschreibung ist Information aus zweiter Hand.
Was Claude wirklich stolpern lässt, sind diese 6 Kategorien.
Im CLAUDE.md von Pickful stehen Zeilen wie diese:
Propshaft (not Sprockets)
ImportMap (no JavaScript bundler)
Hotwire: Turbo Frames, Turbo Streams, Stimulus
Lexxy gem overrides ActionText:
config.lexxy.override_action_text_defaults = false
Jede Zeile wehrt sich gegen die Default-Vermutung. Sieht Claude ein Rails-Projekt, sind seine Default-Annahmen:
Ohne diese Zeilen im CLAUDE.md: Bittest du Claude um ein neues JS-Feature, installiert er mit hoher Wahrscheinlichkeit Webpacker, editiert package.json, schreibt eine Bundler-Config — alles falsch, und still falsch (die App läuft, aber die Asset-Pipeline ist kontaminiert).
Diese Zeilen im CLAUDE.md sagen Claude: nicht raten, ist bereits entschieden.
PostgreSQL with 4 separate databases:
- primary - Main application data
- cache - Solid Cache storage
- queue - Solid Queue jobs
- cable - Action Cable subscriptions
Schlichte Formulierung, kann aber eine komplette Nacht sparen. Rails 8's Multi-DB-Default ist neues Verhalten — Claude prüft nicht von sich aus, wie viele DBs du nutzt. Eine unscheinbar wirkende Migration landet in der falschen DB, kein Fehler in Dev (alle vier sind PostgreSQL, das Schema migriert überall). Aber in Produktion landet die Solid-Queue-Job-Tabelle im Primary-Backup, oder ein Primary-Modell queryt gegen die Cache-DB — solche Bugs brauchen Tage, bis sie auftauchen.
Zwei Zeilen im CLAUDE.md vs. ein Tag Produktions-Debugging.
/p-{slug} - Short post URLs (4-5 char alphanumeric)
/t-{slug} - Topic URLs (3-4 char alphanumeric)
/s-{code} - Short URL redirects (3-4 char alphanumeric)
/r-{referral} - Referral links
Die Routen sieht Claude in routes.rb, aber die Längenkonventionen (4-5 Zeichen, 3-4 Zeichen) stecken in der Slug-Generierungslogik von Modellen oder Services. Bittest du Claude, einen neuen Short-Link-Typ hinzuzufügen, generiert er wahrscheinlich einen 6-stelligen Slug, UUID-Stil oder reine Ziffern — im Widerspruch zur visuellen Sprache des Gesamtsystems.
Das Wesen dieser "Konventionen": Verstöße erzeugen keinen Fehler, aber der nächste Leser findet den Code schief. Muss geschrieben werden.
VIP status at 400+ points
Posts with 15+ likes are "hot" posts
Beide Zahlen leben irgendwo im Code (User#vip?, Post#hot?-Scope). Das Problem: Ändert Claude etwas in der Nähe — Punktebelohnung anpassen, "Fast-VIP"-Notification ergänzen, Cron zum Anpinnen von Hot Posts schreiben — richtet er die Schwellenwerte an anderen Stellen nicht automatisch aus.
Folge: Du belohnst eine Aufgabe mit 500 Punkten, aber der Text sagt "Du kannst VIP werden" (400 reichen schon); oder Seed-Daten für ein neues Feature mit zu wenig Likes, der 15er-Schwellenwert wird nie überschritten.
Claudes Code-Stärke ist hoch, aber er hat kein systemweites Zahlengefühl. Kritische Schwellenwerte im CLAUDE.md sorgen dafür, dass jedes Gespräch startet mit dem Wissen: "400 und 15 sind besondere Zahlen."
- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules
Die Aufgabe dieser Zeile ist Navigation, nicht Beschreibung.
Ohne sie hat Claude beim Hinzufügen einer neuen Permission-Prüfung drei Möglichkeiten:
unless current_user.admin? hart in einen Controller schreibenauthorize?-Methode im Modell hinzufügenMit "Pundit policies in app/policies/" geht Claude jedes Mal direkt nach app/policies/, um eine Policy-Datei anzulegen — stilistisch einheitlich.
Eine Zeile erspart Claude die "Detektivarbeit" jedes Mal.
Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers
Beim Hinzufügen eines neuen Features sind Claudes Defaults:
Aber dein Projekt braucht tatsächlich:
Verstöße gegen diese "projektweiten externen Constraints" erzeugen einen Haufen Kleinkram danach — Übersetzungen nachziehen, Tests neu schreiben. Im CLAUDE.md nagelst du "Dinge, die bei jedem Mal passieren müssen" ein für alle Mal fest.
Genauso wichtig wie "muss geschrieben werden" ist "nicht schreiben". Die folgenden Kategorien — wenn du sie siehst, löschen:
1. Feature-Beschreibungen
"Jury-System: Nutzer können regelwidrige Inhalte melden, Gemeldetes geht in eine öffentliche Abstimmung, Juroren werden ausgewählt aus..."
→ Claude öffnet topic_review_service.rb und liest genauer, als du es geschrieben hast. Das in jeden neuen Kontext zu stopfen ist reine Verschwendung.
2. Was sich sofort aus Verzeichnisbaum / Gemfile lesen lässt
"app/models/ enthält ActiveRecord-Modelle", "Nutzt Rails 8", "DB ist PostgreSQL"
→ Claude wirft einen Blick auf Projektwurzel und Gemfile und weiß es.
3. Allgemeines Programmierwissen
"Controllers should be thin, delegate to services", "N+1 vermeiden", "Tests für Hauptfeatures schreiben"
→ Das steckt bereits in Claudes Trainingsdaten. Nur schreiben, wenn dein Projekt ungewöhnlich ist — z. B. "wir nutzen absichtlich keinen Service-Layer; Logik lebt in den Controllern".
4. Kontext der aktuellen Aufgabe
"Wir refaktorieren gerade das Zahlungssystem; der Fokus ist..."
→ Das ist Gesprächskontext, kein Projektfakt. Ins CLAUDE.md gekippt, kontaminiert es alle anderen Gespräche.
Setz den Beweis "ich schaffe das" vor die Predigt. Nachdem ich den obigen Abschnitt geschrieben hatte, habe ich mein eigenes Pickful-CLAUDE.md — 238 Zeilen — durch die 5 Fragen geschickt. Ergebnis: ungefähr die Hälfte ist Verschwendung.
Zu streichen (~120 Zeilen):
Der Großteil des Dev-Commands-Blocks (70 Zeilen → 10): bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman sind alles Rails-Standardbefehle, bereits in Claudes Trainingsdaten. Behalten werden nur die drei projektspezifischen: bin/jobs (Solid-Queue-Worker), bin/importmap pin (ImportMap-spezifisch), bin/kamal deploy.
Core-Domain-Models-Liste (35 Zeilen → komplett weg): 20 Modelle mit ihren Rollen aufzulisten, das ist das Paradebeispiel für "CLAUDE.md im README-Stil" — Claude führt ls app/models/ aus oder liest eine Modelldatei, und weiß Bescheid. In jedes Gespräch stopfen ist reine Verschwendung.
Standardeinträge im Tech-Stack (28 Zeilen → 8): Rails 8 / Devise / Pundit / Tailwind / pg_search sind alle aus dem Gemfile sofort ersichtlich. Behalten nur die kontraintuitiven: Propshaft / ImportMap / Lexxy / x402-rails / Grover.
Verstreutes Allgemein-Programmierwissen: "Controllers should be thin", "Use app/jobs/ for async processing", was Request Specs vs. Model Specs testen — Claude macht das standardmäßig. Nur Token-Verbrauch.
Übrig bleiben ~100 Zeilen: URL-Routing-Konventionen, 4-DB-Aufteilung, Schwellenwerte VIP 400 / Hot 15, Lexxy-Override, gegen-Default-Architekturentscheidungen, Pundit-Wegweiser, Locale-Liste, FactoryBot (nicht Fixtures).
Noch wichtiger: Welche Invarianten fehlen bislang?
Beim Auditieren ist mir aufgefallen, was ich hätte ergänzen sollen und nie ergänzt habe:
238 Zeilen auf 100–120 eindampfen, dann 5–10 Zeilen vergessener kritischer Invarianten ergänzen — das ist näher an der "richtigen Dichte" eines CLAUDE.md.
Das ist kein Tutorial. Es ist ein Audit meines eigenen Projekts — und ich habe es auch nicht richtig geschrieben. Die korrekte Form eines CLAUDE.md ist weiter löschen und weiter ergänzen — jedes Projekt, das ein bisschen reifer wird, sollte ein kürzeres, dichteres CLAUDE.md haben.
Jedes Mal, wenn ich etwas ins CLAUDE.md aufnehmen will, gehe ich die folgende Checkliste durch:
Regeln, die alle 5 bestehen, bleiben; eine, die irgendeine nicht besteht, wird gelöscht oder neu geschrieben.
Der korrekte Einsatz von CLAUDE.md ist nicht "das Projekt vorstellen", sondern das stillschweigende Wissen zwischen dir und der Codebase zu komprimieren, das Claude durch Code-Lesen nie nachholen kann — ungewöhnliche Entscheidungen, unsichtbare Schwellenwerte, default-widersprüchliche Konventionen, projektweite externe Constraints.
Jede Zeile, die du hinzufügst, sollte eine Frage beantworten: "Kann Claude das nicht aus dem Code lesen?" Nicht — bleibt. Kann — raus.
Ein so geschriebenes CLAUDE.md ist meistens mehr als halb so kurz wie die Erstfassung, aber pro Gespräch mehr wert als tausende Worte Feature-Beschreibung. Und es ist immer "noch nicht fertig" — jedes neue Feature bringt eine weitere Invariante zum Vorschein, die hätte drinstehen sollen, und einen Absatz, der jetzt raus kann. Löschen und ergänzen, löschen und ergänzen — das ist die tägliche Pflege des CLAUDE.md.