Max Nardit
← Artikel
10 mindev

Claude Code permissions.denyInsteadOfPrompting: Die Nacht-Session wartet nicht mehr, und abgelehnte Arbeit erzeugt keinen Fehler

Die neue Einstellung beseitigt das Warten, nicht den Fehlschlag. Ein abgelehnter Aufruf endet mit sauberem Exit und einem Success-Subtype, also ist die Einstellung nur die halbe Einrichtung: Die andere Hälfte ist ein Protokoll dessen, was abgelehnt wurde. Dasselbe Release gibt ihr mehr abzulehnen: Es nimmt dem Auto Mode den Fallback für Sessions, die keine Feature Flags abrufen.

Inhalt

Eine Claude-Code-Session, die über Nacht allein läuft, kann an einem Permission-Prompt hängen bleiben, den bis zum Morgen niemand sieht. Die bisherigen Auswege kosten jeweils etwas. bypassPermissions fragt gar nicht mehr. dontAsk lehnt alles ab, was einen Prompt auslösen würde, ist aber ein Permission-Modus und ersetzt damit den Auto Mode. --permission-prompts none macht dasselbe für einen einzelnen claude -p-Lauf. Claude Code 2.1.319, erschienen am 6. November, bringt permissions.denyInsteadOfPrompting: eine Einstellung, die alles ablehnt, was auf einen Menschen warten würde, in jeder Art von Session und unter jedem Modus, den du gerade nutzt.

Dasselbe Release gibt dieser Einstellung mehr abzulehnen. Bei abgeschalteter Telemetrie oder abgeschalteten Feature Flags fallen Auto-Mode-Aufrufe, die der Server nicht prüfen kann, nicht mehr auf die eigenen Classifier-Requests von Claude Code zurück. Stattdessen wird nachgefragt, und mit der Einstellung heißt das: abgelehnt. Die Tests unten habe ich am 11. November mit Claude Code 2.1.322 unter Linux gemacht, mit Haiku 5.5 in einem Wegwerf-Repository. Wo ein Abschnitt nur auf den Release Notes oder der Doku beruht, steht das dabei.

Was macht permissions.denyInsteadOfPrompting in Claude Code?

Sie macht aus einem Permission-Prompt eine Ablehnung. Allow-Regeln, dein Permission-Modus und der Auto-Mode-Classifier entscheiden weiterhin zuerst. Abgelehnt wird nur ein Aufruf, der sonst anhalten und auf einen Menschen warten würde, und Claude bekommt die Anweisung, mit den Teilen der Aufgabe weiterzumachen, die keine Freigabe brauchen. Die Settings-Referenz nennt als Geltungsbereich -p-Läufe, interaktive Terminal-Sessions, die VS-Code-Extension, lokale Desktop-Sessions, Subagents und Agent Teams.

Vier weitere Details stammen aus der Settings-Referenz, getestet habe ich sie nicht:

  • Wo sie stehen darf. In User- oder Managed-Settings oder über --settings. Die .claude/settings.json eines Repositorys kann sie nicht für dich setzen, und ein true in ~/.claude/settings.json oder --settings gewinnt sogar gegen ein false aus den Managed-Settings.
  • Eine Einstellung, kein Modus. Sie liegt unter dem Auto Mode. Der Classifier winkt Routineaufrufe also weiter durch, und die Einstellung fängt nur ab, was sonst bei einem Prompt landen würde.
  • Was sie sonst noch abschaltet. AskUserQuestion, EnterPlanMode und ExitPlanMode verschwinden aus der Tool-Liste, eine Session im Plan Mode kommt also nicht von selbst wieder heraus (Teammates in einem Agent Team behalten die beiden Plan-Tools). MCP-Elicitation-Anfragen werden abgebrochen, außer ein Elicitation-Hook beantwortet sie. Ein canUseTool-Callback oder --permission-prompt-tool wird nicht mehr gefragt. Wo der Hinweis auf abgerechnete Classifier-Requests einen Dialog öffnen würde, entfällt der Hinweis, und die Aktionen, die diese Requests brauchen, werden abgelehnt.
  • Wann sie trotzdem stoppt. Im Auto Mode mit aktiver Einstellung endet der Turn nach 20 abgelehnten Aufrufen in Folge ohne Arbeit dazwischen („20 actions were refused and no work went ahead in between, so Claude stopped.“), und ein -p-Lauf meldet das als error_during_execution. „Weitermachen“ gilt pro Aufruf und ist kein Versprechen, dass der Turn die ganze Nacht durchläuft.

Kann eine Claude-Code-Session mit denyInsteadOfPrompting trotzdem hängen bleiben?

Ja, an den zwei Stellen, die die Einstellung nicht abdeckt: dem Workspace-Trust-Dialog und der Freigabe von Projekt-MCP-Servern aus .mcp.json. Die Doku sagt das, und meine erste interaktive Session blieb genau daran hängen. Ich hatte sie mit aktiver Einstellung in einem Verzeichnis gestartet, das Claude Code noch nicht kannte, und sie stand auf „Quick safety check: Is this a project you created or one you trust?“, bis ich geantwortet habe.

Für eine interaktive oder Hintergrund-Session gehört damit ein Schritt zur Einrichtung: Claude Code einmal von Hand in dem Verzeichnis starten, das der Job nutzen wird, den Trust-Dialog und eventuelle Projekt-MCP-Server bestätigen, dann beenden. Laut Permissions-Doku hängt der Trust am Repository-Root, und ein Worktree nutzt den Root des Haupt-Checkouts. Ein neuer Worktree eines vertrauten Repositorys fragt also nicht erneut, ein frischer Clone an einem neuen Ort schon. Ein claude -p-Lauf zeigt keinen der beiden Dialoge: Er verbindet Projekt-MCP-Server ohne Rückfrage und ignoriert die Allow-Regeln des Projekts einfach, bis der Ordner vertraut ist. In einem tmux-Pane ist der Trust-Dialog noch tückischer, weil die Standardoption No, exit ist. Was ein verirrtes Enter mit Dialogen in Claude Code anrichtet, steht im tmux-Artikel.

Wie richtest du Claude Code denyInsteadOfPrompting für eine Nacht-Session ein?

Für die Einstellung brauchst du 2.1.319 oder neuer. 2.1.321 hat außerdem drei Permission-Checks repariert, die wichtig sind, wenn niemand zuschaut (ein Read-only-Bash-Befehl, der ohne Prompt außerhalb der Sandbox lief, ein Schreibzugriff, der vom falschen Ordner aus bewertet wurde, und Deny-Regeln, die die Datei übersahen, die ein cp oder mv in ein Verzeichnis schreibt). Mit einer älteren Version als 2.1.321 würde ich das Rezept nicht einsetzen. Das Beispiel aus der Doku setzt die Einstellung in die User-Settings, neben den Start-Modus:

json
{
  "permissions": {
    "defaultMode": "auto",
    "denyInsteadOfPrompting": true
  }
}

Diese Datei gilt für jede Session, die du startest, auch für die, vor denen du selbst sitzt. Wenn sie nur für den unbeaufsichtigten Job gelten soll, übergib sie beim Start:

shell
claude --permission-mode auto \
  --settings '{"permissions":{"denyInsteadOfPrompting":true}}'

Für einen einzelnen claude -p-Lauf gibt es das ältere Flag --permission-prompts none (ab v2.1.259). Laut Headless-Doku zählt es vor allem dann, wenn sonst ein canUseTool-Callback aus dem Agent SDK oder ein --permission-prompt-tool gefragt und abgewartet würde. Ein -p-Lauf ohne einen solchen Host lehnt Prompts ohnehin schon ab, das bestätigt auch mein Kontrolllauf weiter unten. Die Einstellung deckt zusätzlich interaktive und Hintergrund-Sessions ab.

Aus der Begründung in der Doku folgt, dass die Einstellung in bypassPermissions wenig ändert, weil dort kaum etwas einen Prompt auslöst. Diese Kombination habe ich nicht ausprobiert. Sinnvoll ist sie im Auto Mode oder mit engen Allow-Regeln, wo manche Aufrufe weiterhin bei einem Prompt landen.

Wie sieht ein abgelehnter Aufruf in Claude Code mit denyInsteadOfPrompting aus?

In einer interaktiven Session gibt es überhaupt keinen Dialog. Ich habe Claude Code in einem tmux-Pane im manuellen Modus gestartet, die Einstellung über --settings übergeben und um das Anlegen einer Datei gebeten. Der Aufruf zeigte „Not run · permission denied“, und Claude bekam das hier als Tool-Ergebnis (Pfad gekürzt):

Permission for this tool use was denied. It requires approval, and this session has no approval surface — nobody can answer a permission prompt here — so it was denied automatically. The action was NOT performed; do not claim it succeeded, and do not retry it: this action, and anything else that requires approval, will be denied the same way for the rest of this session. Tell the user what was blocked and why you needed it, then continue with the parts of the task that do not require approval. What required approval: Claude requested permissions to write to /tmp/dipt/hello.txt, but you haven't granted it yet.

Claude hat sich nur zur Hälfte daran gehalten. Der erste Versuch war eine Shell-Umleitung. Als die abgelehnt wurde, probierte Claude trotzdem das Write-Tool, und auch das wurde abgelehnt. Erst danach kam die Meldung „Not done: I couldn’t create hello.txt“, mit beiden Versuchen und dem Vorschlag, einen !-Befehl von Hand auszuführen. Haiku nahm denselben zweiten Weg auch in einem der beiden -p-Läufe mit der Einstellung. Eine zweite interaktive Session, die date > now.txt ausführen sollte, hörte nach der ersten Ablehnung auf und schrieb, sie habe keinen anderen Weg versucht, „since that would get around the denial“. Gleiches Modell, gleiche Anweisung, zwei Verhaltensweisen: „do not retry“ ist Text, den das Modell liest, keine Sperre.

In claude -p ist die Einstellung schwerer zu erkennen, weil ein -p-Lauf ohne Permission-Host Prompts ohnehin ablehnt. Ich habe dieselbe Aufgabe mit und ohne Einstellung laufen lassen. Beide Läufe endeten mit Exit 0, "subtype": "success" und "is_error": false, beide führten den abgelehnten Aufruf in permission_denials, und beide gaben im stream-json eine permission_denied-Nachricht aus. Der Unterschied lag in der Nachricht: Ohne die Einstellung bekam Claude nur „Claude requested permissions to write to … but you haven’t granted it yet“, und das permission_denied-Event hatte keinen Grund. Mit der Einstellung bekam Claude den vollen Do-not-retry-Text von oben, und das Event trug einen Grund:

json
"decision_reason": "no approval surface in this session; permission request denied automatically"

Wie erkennst du Arbeit, die Claude Code unter denyInsteadOfPrompting übersprungen hat?

Nicht am Exit-Code. Jeder abgelehnte Aufruf in meinen -p-Läufen endete mit Exit 0 und einem Success-Subtype, und die interaktiven Sessions gingen einfach zurück in den Leerlauf. Die Zusammenfassungen von Haiku waren jedes Mal ehrlich, aber eine Zusammenfassung ist die Darstellung des Modells über seine eigene Arbeit, und ein Nachtjob sollte nicht davon abhängen, dass jemand Prosa liest. Zwei mechanische Prüfungen funktionieren.

Bei -p-Läufen übernimmst du Claudes eigenen Exit-Code und lässt den Lauf bei allem anderen scheitern, was nicht stimmt, abgelehnte Aufrufe eingeschlossen:

shell
claude -p "$TASK" --permission-mode auto --output-format json \
  --settings '{"permissions":{"denyInsteadOfPrompting":true}}' > run.json
rc=$?
python3 - "$rc" <<'EOF' || exit 1
import json, sys
rc = int(sys.argv[1])
try:
    d = json.load(open("run.json"))
except Exception:
    sys.exit("no readable result")
n = len(d.get("permission_denials", []))
print(f"exit={rc} subtype={d.get('subtype')} is_error={d.get('is_error')} refused={n}")
sys.exit(1 if rc or d.get("is_error") or d.get("subtype") != "success" or n else 0)
EOF

Das sagt dir, dass der Lauf auf keine Ablehnung und keinen Fehler gestoßen ist. Ob die Aufgabe erledigt ist, sagt es nicht. Diese Prüfung gehört zur Aufgabe selbst.

Bei interaktiven und Hintergrund-Sessions gibt es kein Ergebnisobjekt, also protokollierst du die Anfrage in dem Moment, in dem sie entsteht. Der naheliegende Hook, PermissionDenied, feuert hier nicht: Laut Hooks-Referenz läuft er nur, wenn der Auto Mode einen Aufruf ablehnt, nicht bei einer Deny-Regel oder einem Prompt, den niemand beantworten kann. PermissionRequest läuft, bevor Claude Code den Prompt anzeigen würde, sieht den Aufruf also, bevor die Einstellung ihn ablehnt. Ein Hook, der die Anfrage aufschreibt und keine Entscheidung zurückgibt, lässt die Ablehnung bestehen. Speichere ihn als ~/.claude/hooks/log-request.py und mach ihn mit chmod +x ausführbar:

python
#!/usr/bin/env python3
# PermissionRequest hook: record the call, decide nothing.
import json, os, sys, datetime
e = json.load(sys.stdin)
row = {
    "t": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"),
    "session": e.get("session_id"),
    "tool": e.get("tool_name"),
    "input": e.get("tool_input"),
}
with open(os.path.expanduser("~/.claude/prompt-requests.log"), "a") as f:
    f.write(json.dumps(row) + "\n")

Lade ihn in derselben --settings-Datei wie die Einstellung. Dann läuft er nur für den unbeaufsichtigten Job und nicht für die Prompts, die du tagsüber von Hand freigibst:

json
{
  "permissions": { "denyInsteadOfPrompting": true },
  "hooks": {
    "PermissionRequest": [
      { "matcher": "*", "hooks": [{ "type": "command", "command": "~/.claude/hooks/log-request.py" }] }
    ]
  }
}

So geladen habe ich ihn in einem -p-Lauf und in einer interaktiven tmux-Session. Er protokollierte im ersten Fall das abgelehnte Write, im zweiten den abgelehnten Bash-Aufruf, und beide Aufrufe blieben abgelehnt. Die zweite Zeile, Session-ID gekürzt:

json
{"t": "2026-11-11T02:25:43+00:00", "session": "7a533308-…", "tool": "Bash", "input": {"command": "date > now.txt", "description": "Write current date to now.txt"}}

Das Log hat drei Grenzen. Eine Zeile ist eine Anfrage, die einen Prompt ausgelöst hätte. Eine Ablehnung ist sie nur, solange die Einstellung aktiv ist und kein anderer PermissionRequest-Hook den Aufruf freigibt. Wenn du also auch freigebende Hooks nutzt, bestimmen die, was eine Zeile bedeutet. Filtere nach session, nicht nach Zeit, denn die Datei teilen sich alle Sessions. Und ein leeres Log beweist nichts, solange nicht sicher ist, dass der Hook gelaufen ist: Schick einmal ein Beispiel-Event per Pipe ins Skript und mach einen Aufruf, von dem du weißt, dass er abgelehnt wird, bevor du einer leeren Datei traust. Laut Settings-Referenz werden manche Aufrufe abgelehnt, bevor überhaupt ein Hook gefragt wird (das Löschen eines kritischen Pfads im Auto Mode, das keine Ask-Regel abdeckt). Das Log ist also eine Untergrenze, keine vollständige Liste. Für Ablehnungen durch den Auto Mode deckt ein PermissionDenied-Hook den Rest ab. Warum ein Hook besser zum Lenken als zum Absichern taugt, steht im Hooks-Artikel.

Was hat sich im Claude Code Auto Mode bei abgeschalteter Telemetrie oder Feature Flags geändert?

Ein Fallback auf Session-Ebene ist weggefallen. Laut der Doku zu den Permission-Modi bittet seit 2.1.282 eine Session, die keine Feature Flags abruft, standardmäßig den Server, Auto-Mode-Aktionen zu prüfen. Wenn der Server die Session gar nicht prüft (ein Gateway, das die Review-Felder verwirft, eine Plattform oder Zugangsdaten ohne Server-Checks), fiel Claude Code bisher für den Rest der Session auf seine eigenen Classifier-Requests zurück. Die Release Note zu 2.1.319 ändert das für Sessions „with telemetry or feature flags off, including behind LLM gateways“: Aufrufe, die der Server nicht prüfen kann, „are asked about or denied, with no fallback to the built-in classifier“. Mit aktivem denyInsteadOfPrompting wird aus „asked about“ ein „denied“. Eine einzelne Aktion, die von einem prüfenden Server kein Urteil bekommt, wurde schon vor diesem Release abgelehnt. Dieser Teil ist nicht neu.

Wen es trifft, hängt mehr von der Verbindung ab als vom Telemetrie-Schalter. In meiner Session mit direkter Verbindung zur Anthropic API schaltete das Abschalten der Telemetrie das Server-Review nicht ab: Mit DISABLE_TELEMETRY=1 zeigte /status im Auto Mode Auto mode server: Enabled, und ein -p-Lauf im Auto Mode führte einen Shell-Befehl ohne Ablehnung aus. Gemeint ist die Änderung für ein LLM-Gateway oder einen Proxy, der die Review-Anfrage oder ihre Ergebnisse verwirft. Solche Sessions bekamen bisher die eigenen Classifier-Requests von Claude Code und bekommen jetzt Prompts oder Ablehnungen. Keiner meiner Tests lief hinter einem Gateway, dieser Teil beruht also auf der Release Note. Was ein Gateway unverändert durchreichen muss, steht im Gateway-Artikel. Bedrock, Vertex und Foundry sind laut derselben Note nicht betroffen.

Zwei Zeilen dieser Note liest man leicht falsch. CLAUDE_CODE_DISABLE_FEATURE_FLAGS, ebenfalls neu in 2.1.319, stoppt das Abrufen der Flags und lässt die Telemetrie an, stellt den Fallback aber nicht wieder her: Die Note nennt „telemetry or feature flags off“ in einem Atemzug. Der Wechsel weg von DISABLE_TELEMETRY ändert also, was du sendest, nicht, wie sich der Auto Mode verhält. CLAUDE_CODE_AUTO_MODE_SERVER=0 stellt den Fallback dagegen wieder her, indem der Server gar nicht erst gefragt wird. Die Doku weist darauf hin, dass diese Classifier-Requests bei Enterprise-Plänen und bei API-, Bedrock-, Vertex- und Foundry-Accounts zum Token-Verbrauch zählen. Beim Server-Review fallen diese Kosten nicht an.

Die Seite zu den Permission-Modi beschreibt, Stand 11. November, weiterhin den Fallback auf Session-Ebene ohne die Ausnahme aus 2.1.319. Für eine Session mit abgeschalteter Telemetrie oder abgeschalteten Flags gilt die Release Note, bis die Doku nachzieht.

Wie prüfst du, welchen Weg der Claude Code Auto Mode nimmt?

Führ /status aus und lies die Zeile Auto mode server. In meinen Sessions stand dort Enabled mit DISABLE_TELEMETRY=1 allein und Disabled, sobald CLAUDE_CODE_AUTO_MODE_SERVER=0 dazukam. 2.1.319 hat außerdem behoben, dass die Zeile Enabled zeigte, obwohl das Review die Session gar nicht erreichte, etwa hinter einem Gateway, das es verwirft. In älteren Versionen bewies ein Enabled dort also nichts. Sobald du weißt, dass der Server nicht prüft, zeigt der Artikel zur Auto-Mode-Einrichtung, wie du herausfindest, auf welchem Modell der eigene Classifier von Claude Code läuft.

Dasselbe Release hat einen -p-Exit-Code korrigiert: Wenn der Auto Mode Aktionen ablehnte, weil kein Safety-Check verfügbar war, endete claude -p bisher mit 0 und endet jetzt bei Text- und JSON-Ausgabe mit 1. Der Fix betrifft den Auto Mode ohne Check, nicht Ablehnungen allgemein. Meine Ablehnungen im Default-Modus endeten weiterhin mit 0, deshalb zählt das Skript oben auch permission_denials. Laut Hooks-Referenz sieht ein PermissionDenied-Hook eine Ablehnung ohne Urteil mit einem reason, der mit „Auto mode could not evaluate this action and is blocking it for safety“ beginnt. Daran unterscheidest du einen fehlenden Check von einem Classifier, der sich den Aufruf angesehen und Nein gesagt hat.

Solltest du Claude Code denyInsteadOfPrompting einschalten?

Für eine interaktive oder Hintergrund-Session, die niemand beobachtet: ja, und nur zusammen mit dem Log. Dort tauscht die Einstellung einen Fehlschlag, den du siehst, gegen einen, den du nachschlagen musst. Eine hängende Session fällt morgens sofort auf: Das Pane steht auf einem Dialog, und sonst ist nichts passiert. Eine Session mit der Einstellung beendet ihren Turn, und der abgelehnte Aufruf steht in der Zusammenfassung, bei einem -p-Lauf in permission_denials und, falls eingerichtet, im Hook-Log. Von selbst meldet nichts einen Fehler. Für claude -p ändert die Einstellung weniger: Diese Läufe haben schon vorher abgelehnt, und was dazukommt, ist der Do-not-retry-Text, den einer meiner Läufe ignoriert hat. Laut Doku macht Claude mit den Schritten weiter, die keine Freigabe brauchen. Meine Aufgaben hatten jeweils nur einen Schritt, sie zeigen also nur die Ablehnung.

Die Änderung am Auto Mode weist in dieselbe Richtung. Hinter einem Gateway, das das Server-Review verwirft, macht 2.1.319 aus einem stillen Fallback Prompts, und mit aktiver Einstellung mehr abgelehnte Aufrufe. Ohne das Log ist eine hängende Session der ehrlichere Fehlschlag.

01 / Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit@mnardit

02 / Weitere Artikel