Der Claude Code Headless-Modus ist das Flag -p. claude -p "dein Prompt" führt Claude Code einmal ohne interaktive Oberfläche aus: Es liest deinen Prompt (und alles, was du per Pipe hineinschickst), arbeitet mit den Tools, die du erlaubst, gibt das Ergebnis aus und beendet sich. Exit-Code 0 bedeutet Erfolg, alles andere bedeutet, dass der Lauf fehlgeschlagen ist. Damit ist es ein normales Kommandozeilenwerkzeug, das du in Shell-Skripte, Cron-Jobs, Git-Hooks und CI-Pipelines einbauen kannst.
Dieser Leitfaden behandelt die Flags, die du wirklich brauchst: Ausgabeformate, Tool-Berechtigungen, Turn- und Budget-Limits, --bare, das Fortsetzen von Sessions und das Durchleiten von Daten, gefolgt von fertigen Mustern für Skripte, Cron und CI und der Frage, wann der Wechsel zum Agent SDK sinnvoll ist. Jedes Flag hier ist mit der offiziellen CLI-Referenz mit Stand Oktober 2026 abgeglichen.
Die Grundlagen
claude -p "What does the auth module do?"
-p und --print sind dasselbe Flag. Die meisten interaktiven Optionen lassen sich damit kombinieren, etwa --model, --effort, --append-system-prompt und --mcp-config. Einige Optionen gibt es nur im Print-Modus, darunter --max-turns, --max-budget-usd, --json-schema und --no-session-persistence.
Zwei Details sind für Skripte wichtig:
- Wenn du ein ungültiges Flag übergibst, landet der Fehler auf stderr, bevor der Lauf startet.
- Wenn innerhalb des Laufs etwas fehlschlägt, etwa eine fehlende Authentifizierung, wird der Fehler als Ergebnis auf stdout ausgegeben. Prüfe den Exit-Code, nicht nur die Ausgabe.
Daten hineinleiten, Ergebnisse umleiten
Der Headless-Modus liest stdin, also fließen Logs, Diffs und Dateien hinein wie bei jedem Unix-Werkzeug:
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
git diff main | claude -p "Review this diff for bugs. List each as file:line and one sentence."
Per Pipe übergebenes stdin ist auf 10MB begrenzt. Darüber beendet sich Claude Code mit einem Fehler; schreib große Eingaben deshalb in eine Datei und nenne stattdessen den Pfad im Prompt.
Du kannst es auch in die package.json einbinden, wie es die offizielle Dokumentation mit einem Tippfehler-Linter tut:
{
"scripts": {
"lint:claude": "git diff main | claude -p \"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else.\""
}
}
Ein Ausgabeformat wählen
--output-format kennt drei Werte:
| Format | Was du bekommst | Wofür |
|---|---|---|
text (Standard) | Die endgültige Antwort als reiner Text | Menschen, Dateien, einfache Skripte |
json | Ein JSON-Objekt am Ende | Skripte, die die Antwort plus Metadaten brauchen |
stream-json | Ein JSON-Event pro Zeile, während es passiert | Live-Fortschritt, eigene UIs, Logging |
Das JSON-Objekt enthält die Antwort in result, dazu session_id, Nutzungszahlen und total_cost_usd. Diese Kosten sind eine clientseitige Schätzung und können von deiner Rechnung abweichen.
claude -p "Summarize this project" --output-format json | jq -r '.result'
Strukturierte Ausgabe mit —json-schema
Wenn ein Skript Daten statt Prosa braucht, übergib ein JSON Schema. Das validierte Ergebnis landet in structured_output:
claude -p "Extract the main function names from auth.py" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'
Endet der Lauf ohne gültige strukturierte Ausgabe, etwa weil Claude abgelehnt hat, fehlt das Feld. Prüfe es, bevor du es nutzt:
out=$(claude -p "Extract the main function names from auth.py" --output-format json --json-schema "$(cat schema.json)")
echo "$out" | jq -e '.structured_output' > /dev/null || { echo "no structured output" >&2; exit 1; }
Streaming-Events
stream-json erfordert --verbose. Ergänze --include-partial-messages, um Tokens zu bekommen, während sie erzeugt werden. Das hier gibt nur den gestreamten Text aus:
claude -p "Write a poem" --output-format stream-json --verbose --include-partial-messages | \
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
Das erste Event ist normalerweise system/init mit Modell, Tools, MCP-Servern und Plugins. Die letzte Zeile ist eine result-Nachricht mit dem endgültigen Text, den Kosten und den Session-Metadaten. Wiederholungsversuche erscheinen als system/api_retry-Events. Ein nützliches CI-Gate: Wenn du --mcp-config übergibst, wird ein Server-Eintrag, der die Validierung nicht besteht, übersprungen, ohne den Lauf scheitern zu lassen, und er erscheint in mcp_server_errors im init-Event. Lass den Job fehlschlagen, wenn dieses Array nicht leer ist.
Festlegen, was der Lauf darf
In einem Skript ist niemand da, der eine Berechtigungsabfrage beantwortet; entscheide also vorab, was Claude darf.
--allowedTools gibt Tools in der Syntax der Berechtigungsregeln vorab frei:
claude -p "Look at my staged changes and create an appropriate commit" \
--allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
Das Leerzeichen vor * ist wichtig: Bash(git diff *) passt auf Befehle, die mit git diff beginnen, während Bash(git diff*) auch git diff-index erfassen würde.
--permission-mode legt die Grundlinie für alles fest, was nicht aufgelistet ist. Mit Stand Oktober 2026 (Claude Code v2.1.302 und neuer) startet ein -p-Lauf im Manual-Modus, wenn nichts einen Modus setzt, und ein Aufruf, der eine Freigabe braucht, wird verweigert. Nützliche Werte:
| Modus | Verhalten in einem Headless-Lauf |
|---|---|
dontAsk | Alles, was nicht vorab freigegeben ist, wird abgelehnt. Die berechenbare Wahl für Skripte |
acceptEdits | Dateibearbeitungen sind ohne Nachfrage erlaubt |
plan | Nur lesen und planen, keine Bearbeitungen |
auto | Ein Klassifikator prüft jede Aktion; eine Blockade verweigert diese Aktion, und der Lauf geht weiter |
bypassPermissions | Überhaupt keine Prüfungen. Nur in einem Wegwerf-Container oder einer VM |
--tools schränkt ein, welche eingebauten Tools es überhaupt gibt, und --disallowedTools fügt Deny-Regeln hinzu. --permission-prompts none sagt Claude, dass niemand Anfragen freigeben kann, damit es sie nicht immer wieder versucht; Verweigerungen stehen in permission_denials im Endergebnis. Warum das Umgehen der Berechtigungen auf deinem eigenen Rechner riskant ist, erklärt Claude Code Berechtigungen überspringen.
Setz jedem unbeaufsichtigten Lauf Grenzen
--max-turns 20stoppt nach so vielen agentischen Turns und beendet sich mit einem Fehler. Standardmäßig gibt es kein Limit.--max-budget-usd 5.00stoppt, wenn die geschätzten Ausgaben den Betrag erreichen. Es nutzt die clientseitige Schätzung, und die Ausgaben können die Grenze leicht überschreiten; lass also Spielraum.timeoutaus deiner Shell oder deinem CI-Schritt lohnt sich trotzdem. Wenn du einen Lauf mit SIGTERM stoppst, beendet sich Claude Code mit Code 143, speichert für den unfertigen Turn kein Ergebnis und führt trotzdem dieSessionEnd-Hooks aus.
—bare für reproduzierbare Läufe
Ohne --bare lädt claude -p dasselbe wie eine interaktive Session: Hooks, Skills, Plugins, MCP-Server, Auto-Memory und CLAUDE.md, aus dem Arbeitsordner und aus ~/.claude. Derselbe Befehl verhält sich also auf deinem Laptop anders als auf einem CI-Runner.
claude --bare -p "Summarize README.md" --allowedTools "Read"
--bare überspringt diese ganze Suche und startet schneller. Du übergibst nur, was der Lauf braucht:
| Zu laden | Flag |
|---|---|
| Zusätzliche Anweisungen | --append-system-prompt, --append-system-prompt-file |
| Einstellungen | --settings <file-or-json> |
| MCP-Server | --mcp-config <file-or-json> |
| Eigene Agenten | --agents <file-or-json> |
| Ein Plugin | --plugin-dir <path> |
Der Bare-Modus liest nie OAuth-Zugangsdaten oder den System-Schlüsselbund. Mit der Anthropic API setzt du ANTHROPIC_API_KEY oder gibst einen apiKeyHelper im --settings-JSON an. Die offizielle Dokumentation empfiehlt --bare für geskriptete Aufrufe und sagt, dass es in einem künftigen Release zum Standard für -p wird; wenn du es jetzt ausdrücklich schreibst, bleiben deine Skripte stabil, wenn es so weit ist.
Noch ein Grund, darauf zu achten: Ohne --bare führt ein -p-Lauf in einem frisch geklonten Repo dessen Projekt-Hooks und .mcp.json-Server ohne Vertrauensdialog aus. Richte keinen Headless-Lauf ohne --bare auf Code, den du nicht geprüft hast.
Sessions fortsetzen und wieder aufnehmen
Headless-Läufe werden wie interaktive gespeichert, also kannst du Schritte verketten:
# First request
claude -p "Review this codebase for performance issues"
# Continue the most recent conversation
claude -p "Now focus on the database queries" --continue
claude -p "Generate a summary of all issues found" --continue
Wenn mehrere Läufe gleichzeitig stattfinden, kann --continue den falschen erwischen. Greif stattdessen die Session-ID ab:
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"
--resume findet die Session per ID aus jedem Ordner auf dem Rechner und akzeptiert auch einen Namen oder den Pfad zum .jsonl-Transkript einer Session. --fork-session startet einen neuen Zweig aus der wieder aufgenommenen Unterhaltung, statt an sie anzuhängen, und --no-session-persistence verhindert, dass einmalige Läufe überhaupt gespeichert werden.
Muster für Skripte, Cron und CI
Eine Schleife über Dateien
Die offizielle Best-Practices-Seite hat eine gute Vorlage für eine mechanische Änderung über viele Dateien:
for file in $(cat files.txt); do
claude --bare -p "Migrate $file to the new logger API and commit. Reply OK or FAIL." \
--allowedTools "Read,Edit,Bash(git add *),Bash(git commit *)" \
--permission-mode dontAsk \
--max-turns 20
done
Lass es zuerst auf drei Dateien laufen, lies die Commits, korrigiere den Prompt und führe dann den Rest aus. Ein Lauf, der das Turn-Limit erreicht, endet mit einem Fehler, den deine Schleife protokollieren kann.
Ein Review-Skript
gh pr diff "$1" | claude -p \
--append-system-prompt "You are a security engineer. Review for vulnerabilities." \
--output-format json
Speichere es als review.sh und führe bash review.sh 123 aus; der Review-Text steht in result.
Ein nächtlicher Cron-Job
# crontab -e
0 2 * * * cd /srv/app && claude --bare -p "Run the test suite, fix failing tests, commit on the current branch. Do not push." --allowedTools "Read,Edit,Bash(npm test *),Bash(git add *),Bash(git commit *)" --permission-mode dontAsk --max-turns 30 --output-format json >> /var/log/claude-nightly.jsonl 2>> /var/log/claude-nightly.err
Cron läuft mit einer minimalen Umgebung; stell also sicher, dass claude in dessen PATH liegt und ANTHROPIC_API_KEY für diesen Benutzer gesetzt ist. Lass es auf einem Branch oder in einem Worktree laufen, nicht auf main. Wenn du --worktree ergänzt, beachte, dass -p --worktree-Läufe nicht für dich aufgeräumt werden; entferne sie mit git worktree remove <path>. Für das komplette Nacht-Setup mit Budgets und Review am Morgen siehe Claude Code über Nacht laufen lassen, und für Timer auf jedem Betriebssystem Claude-Code-Aufgaben planen.
CI-Pipelines
Nutze in CI --bare, bewahre den API-Key im Secret-Speicher der CI auf und behandle den Exit-Code als Ergebnis des Schritts. Speziell für GitHub nimmt dir die offizielle Action das ab, mit PR-Kommentaren und @claude-Erwähnungen: siehe Claude Code GitHub Actions. Wenn du ein Abo statt eines API-Keys nutzt, gibt claude setup-token ein langlebiges OAuth-Token für CI und Skripte aus; es wird nicht gespeichert, also leg es sofort als Secret ab. Denk daran, dass --bare OAuth ignoriert; ein Abo-Token braucht also einen Lauf ohne --bare.
Stolperfallen
- Skills und Slash-Befehle. Benutzer-Skills funktionieren, wenn du
/skill-namein den Prompt schreibst. Befehle, die es nur in der Terminal-Oberfläche gibt, etwa/login, funktionieren nicht. - Stille Tool-Verweigerungen. Wenn Claude sagt, es konnte etwas nicht tun, prüfe
permission_denialsim JSON-Ergebnis und ergänze die fehlende Regel in--allowedTools. - Hintergrundarbeit. Ein
-p-Lauf kann offen bleiben, um auf Hintergrundbefehle oder Subagents zu warten, die Claude gestartet hat; standardmäßig hört er nach 10 Minuten Leerlauf auf zu warten. - Den System-Prompt ersetzen.
--system-promptersetzt den Standard-Prompt vollständig, einschließlich des Git-Status-Snapshots. Nimm lieber--append-system-prompt, außer du willst bewusst bei null anfangen.
Wann der Wechsel zum Agent SDK lohnt
claude -p ist die Engine des Agent SDK hinter einer CLI. Sobald dein Skript anfängt, Streams zu parsen, Freigaben zu behandeln oder viele Sessions zu verwalten, wechsle zum Agent SDK für Python oder TypeScript. Es gibt dir native Nachrichtenobjekte, Callbacks für Tool-Freigaben und strukturierte Ausgaben im Code, ohne den Umweg über die Shell. Der Beitrag Claude Code Best Practices zeigt, was du einrichten solltest, bevor ein Lauf unbeaufsichtigt bleibt.
So geht es mit AgentVera
Headless-Läufe sind Fire-and-Forget; manchmal willst du dieselbe Automatisierung mit einem Terminal, dem du zusehen kannst. In AgentVera schicken geplante Aufgaben (in jedem Plan) nach einem Cron-Zeitplan einen Prompt an einen Agenten, während die App geöffnet ist, und der Agent läuft in einem echten Pane, das du öffnen und steuern kannst. Automatisierungen (Pro und Team) starten einen Agenten, wenn die CI rot wird oder ein PR einen Kommentar bekommt, und pushen nie auf den Standard-Branch. Die CI/CD-Zentrale listet Läufe von GitHub Actions, GitLab und Jenkins auf und kann ein fehlgeschlagenes Log an einen Agenten übergeben. Die Nachtschicht (Pro und Team) arbeitet eingereihte Aufgaben über Nacht in einem Worktree auf einem night/<date>-<slug>-Branch ab, committet ohne zu pushen und hinterlässt einen Bericht für den Morgen. Lade AgentVera herunter, wenn du diese Läufe sehen willst, statt sie in einer Logdatei zu vergraben.
Fazit
Das Grundrezept für Claude Code im Headless-Modus ist kurz: claude --bare -p "clear task with a check", eine ausdrückliche --allowedTools-Liste, --permission-mode dontAsk, --max-turns, --output-format json und ein Exit-Code, den dein Skript beachtet. Fang klein an, lies, was die ersten Läufe produzieren, und leg es erst dann auf einen Timer.