SpyBara
Go Premium

plugins/mods/events.md 2026-10-01 23:59 UTC to 2026-10-02 22:00 UTC

This page contains 140 additions and 113 deletions.

2026
Thu 1 23:59 Fri 2 22:59

Mit einem Mod auf Events reagieren

Verarbeiten Sie Claude Code-Events in einem Mod: Beobachten, umschreiben oder beantworten Sie Tool-Aufrufe, Prompts und Turns, filtern Sie, welche Events ein Hook verarbeitet, und planen Sie für andere Mods.

Ein Hook ist ein Event-Handler: eine Funktion, die Claude Code ausführt, wenn ein benanntes Event eintritt. Claude Code löst an jeder Stelle ein Event aus, an der es im Begriff ist, eine Aktion auszuführen, etwa wenn es ein Tool ausführt, einen Prompt absendet, eine Anfrage an das Modell sendet oder eine Sitzung startet oder beendet. Ihr Hook wird ausgeführt, bevor Claude Code handelt, sodass er das Event beobachten, umschreiben oder anstelle von Claude Code beantworten kann. Sie registrieren einen Hook mit on(eventName, handler).

Erstellen Sie Ihren ersten Mod, bevor Sie hier beginnen. Alle Events und ihre genauen Felder finden Sie in der Referenz, oder lesen Sie die Typen für Ihren Build.

Wie ein Hook ein Ereignis verarbeitet

Ein Hook sitzt zwischen einem Ereignis und dem, was Claude Code daraufhin tun würde. Er kann das Ereignis daher beobachten, umschreiben oder selbst beantworten. Er erhält drei Argumente: die Mods-API als $, das Ereignis als e und den nächsten Handler als next. Die Handler für ein Ereignis bilden eine Middleware-Kette. next(e) ruft den nächsten Handler auf, also den Hook eines anderen Mods oder, am Ende der Kette, das eigene Verhalten von Claude Code, und wird zum Ergebnis aufgelöst. Was Ihr Hook mit next macht, bestimmt, welche der drei Möglichkeiten er umsetzt.

Ein Ereignis beobachten

Um ein Ereignis zu beobachten, ohne es zu ändern, erledigen Sie Ihre Arbeit und geben next(e) zurück. Dieser Hook protokolliert jedes Tool, das Claude gleich verwenden wird:

on('tool.call', async ($, e, next) => {
  // Runs before the tool does
  $.ui.log('Claude is about to use ' + e.tool)
  // Pass the event on unchanged
  return next(e)
})

Bevor jedes Tool ausgeführt wird, erscheint im Transkript eine abgeblendete Zeile wie ● my-mod: Claude is about to use Bash, wobei my-mod der Name Ihres Plugins ist. Das Tool wird so ausgeführt, wie es auch ohne den Mod ausgeführt würde.

Um nach dem Ereignis zu handeln, verwenden Sie await next(e), erledigen Ihre Arbeit und geben das Ergebnis zurück. Dieser Hook protokolliert jedes Tool, nachdem es ausgeführt wurde:

on('tool.call', async ($, e, next) => {
  // Let the tool run, and wait for its result
  const result = await next(e)
  // Runs after the tool does
  $.ui.log(e.tool + ' finished')
  // Give the result back unchanged
  return result
})

Die Zeile erscheint jetzt, nachdem jedes Tool abgeschlossen ist. Claude liest in beiden Fällen dasselbe Ergebnis, da der Hook zurückgibt, wozu next(e) aufgelöst wurde.

Ein Ereignis umschreiben

Um zu ändern, worauf Claude Code reagiert, etwa den Text eines Prompts, rufen Sie next mit einer geänderten Kopie des Ereignisses auf. Das Ereignis selbst ist unveränderlich: Es ist tief eingefroren, und eine Zuweisung an ein Feld löst einen Fehler aus. Dieser Hook entfernt bei jedem Prompt vor dem Senden führende und nachfolgende Leerzeichen:

on('prompt.submit', async ($, e, next) => {
  // Pass on a copy of the event with its text changed
  return next({ ...e, text: e.text.trim() })
})

Nachfolgende Handler und Claude Code erhalten den gekürzten Prompt und sehen das Original nie. Sie können auch das Ergebnis ändern: Verwenden Sie await next(e) und geben Sie dann eine Kopie des Ergebnisses zurück, in der ein Feld ersetzt ist.

Ein Ereignis beantworten

Um ein Ereignis selbst zu verarbeiten, geben Sie ein Ergebnis zurück, ohne next aufzurufen. Dadurch wird die Kette abgekürzt, sodass nachfolgende Mods und das eigene Verhalten von Claude Code nicht ausgeführt werden. Dieser Hook lehnt jeden Bash-Befehl ab:

on('tool.call', { tool: 'Bash' }, async () => {
  // No call to next, so the command never runs
  return { deny: 'Bash is turned off in this project. Use the file tools.' }
})

Wenn Claude einen Bash-Befehl versucht, wird der Befehl nicht ausgeführt, und Claude liest den deny-Text als Ergebnis des Tools. Jedes Ereignis hat eine eigene Ergebnisstruktur, die in der Ereignisreferenz aufgeführt ist.

Filtern, welche Ereignisse ein Hook verarbeitet

Um einen Hook nur für bestimmte Ereignisse auszuführen, übergeben Sie einen Filter als zweites Argument an on. Claude Code nennt den Filter einen Matcher. Es handelt sich um ein Objekt, dessen Felder mit denen des Ereignisses verglichen werden, und der Hook wird nur ausgeführt, wenn jedes Feld übereinstimmt. Ein Feld kann ein Wert, ein Array zulässiger Werte oder ein regulärer Ausdruck sein.

Jede Zeile in diesem Beispiel registriert dieselbe Funktion, hook, für eine engere Auswahl von Tool-Aufrufen:

// A string matches one value: Bash calls only
on('tool.call', { tool: 'Bash' }, hook)
// An array matches any value in it: Edit calls and Write calls
on('tool.call', { tool: ['Edit', 'Write'] }, hook)
// A regular expression matches by pattern: every tool of one MCP server
on('tool.call', { tool: /^mcp__github__/ }, hook)

hook wird einmal für einen Bash-, Edit- oder Write-Aufruf ausgeführt und einmal für einen Aufruf eines Tools, dessen Name mit mcp__github__ beginnt. Ein Aufruf eines anderen Tools, etwa Read, stimmt mit keinem der drei überein, sodass hook dafür nicht ausgeführt wird.

Der Ereignisname kann ein Platzhalter sein. 'classic.*' passt auf jedes Einstellungs-Hook-Ereignis. '*' passt auf jedes Ereignis außer den Telemetrie-Ereignissen, die ihren eigenen Namen und einen { to: 'collector' }-Filter verwenden.

Registrieren Sie jedes Ereignis nur einmal pro Matcher. Wenn Sie on zweimal für session.start ohne Matcher aufrufen, kann das Modul nicht geladen werden und meldet on("session.start") is registered twice without a matcher. Fassen Sie alles, was Ihr Mod beim Sitzungsstart tut, in einem Hook zusammen.

Auf das reagieren, was Claude tut

Behandeln Sie diese Events, um einen Tool-Aufruf, einen Prompt oder einen Turn zu sehen oder zu ändern, während er stattfindet. Alle Events und was ein Hook jeweils zurückgeben kann, finden Sie in der Event-Referenz.

Einen Tool-Aufruf absichern oder ändern

Ein tool.call-Hook sieht jedes Tool, das Claude gleich verwenden wird, und kann den Aufruf daher ablehnen, seine Argumente ändern oder ihn durchlassen. tool.call wird ausgelöst, wenn Claude Code gleich ein Tool ausführt, einschließlich Aufrufen eines Subagenten und Aufrufen von MCP-Tools. e.tool ist der Name des Tools, und die Argumente des Tools sind Felder von e, etwa e.command für Bash. Wenn Sie next(e) aufrufen, führt Claude Code die Berechtigungsprüfung und anschließend das Tool aus.

Dieser Hook lehnt einen Bash-Befehl ab, der einen Force-Push ausführt, und teilt Claude den Grund mit:

// The matcher limits the hook to Bash calls, so e.command is the shell command
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
  if (/git push .*--force/.test(e.command)) {
    // Returning without calling next answers the event, so the command never runs
    return { deny: 'Force pushes are not allowed in this repository. Push to a new branch instead.' }
  }
  // Every other command goes on to the permission check and then to Bash
  return next(e)
})

Wenn Claude git push --force versucht, wird der Befehl nicht ausgeführt und es erscheint keine Berechtigungsabfrage, weil der Hook next nie aufruft. Claude liest den deny-Text als Ergebnis des Tools, formulieren Sie ihn also als Anweisung, nach der Claude handeln kann. Jeder andere Bash-Befehl wird so ausgeführt, wie er es ohne den Mod würde.

Um nach der Ausführung eines Tools zu handeln, verwenden Sie await next(e), erledigen Ihre Arbeit und geben zurück, was next Ihnen geliefert hat. Dieser Hook protokolliert jede .mdx-Datei, die Claude ändert, mit $.ui.log, das dem Transkript eine abgeblendete Zeile hinzufügt, die Claude nicht liest:

on('tool.call', { tool: ['Edit', 'Write'] }, async ($, e, next) => {
  // Wait for the permission check and the tool, and keep what they produced
  const result = await next(e)
  // A refused call comes back as { deny }, and a failed one has isError set
  const changed = !result.deny && !result.isError
  if (changed && e.file_path.endsWith('.mdx')) $.ui.log('Claude changed ' + e.file_path)
  // Return the result as it came, so Claude reads what the tool returned
  return result
})

Nachdem Claude eine .mdx-Datei bearbeitet oder geschrieben hat, nennt eine abgeblendete Zeile im Transkript die Datei. Für andere Dateitypen oder für einen abgelehnten oder fehlgeschlagenen Aufruf wird nichts protokolliert. Claudes Sicht auf den Aufruf ändert sich nicht, weil der Hook das erhaltene Ergebnis zurückgibt.

Um einen Aufruf zu ändern, übergeben Sie geänderte Argumente an next. Um einen Aufruf erneut zu versuchen, rufen Sie next(e) noch einmal auf: Ein Hook, der beim ersten Ergebnis isError sieht, kann das Tool ein zweites Mal ausführen und dieses Ergebnis zurückgeben. Um einen Aufruf selbst zu beantworten, geben Sie ein Objekt mit einem result-Feld zurück, etwa { result: 'Skipped by my-mod' }, ohne next aufzurufen. In diesem Fall erscheint keine Berechtigungsabfrage und das Tool wird nicht ausgeführt, sodass das von Ihnen zurückgegebene Ergebnis alles ist, was Claude über den Vorgang erfährt.

Hooks in den verwalteten Einstellungen Ihrer Organisation werden vor dem tool.call-Hook jedes Mods ausgeführt, und eine Blockierung durch einen davon ist endgültig.

Einen Tool-Aufruf zurückhalten, bis der Benutzer entscheidet

Ein Hook kann einen Tool-Aufruf anhalten und den Benutzer fragen, was geschehen soll, bevor er fortgesetzt wird. Ein tool.call-Hook kann await verwenden, bevor er next aufruft oder zurückkehrt, und der Tool-Aufruf bleibt bis dahin ausstehend. Um dem Benutzer die Frage zu stellen, rufen Sie $.ui.ask auf. Es zeigt Ihre Frage über einer nummerierten Liste Ihrer Optionen in dem Dialog an, den Claude verwendet, um Sie etwas zu fragen, und wird mit der Bezeichnung aufgelöst, die der Benutzer auswählt. Nach Ihren Optionen fügt der Dialog eine Zeile zum Eingeben einer anderen Antwort und eine Zeile Chat about this hinzu.

Das Muster RISKY in diesem Beispiel erfasst rm -r, rm -rf, git reset --hard und git push mit --force und übersieht andere Schreibweisen wie git push -f. Dieses Modul fragt nach, bevor es einen Bash-Befehl ausführt, der dem Muster entspricht:

const RISKY = /\brm\s+-rf?\b|\bgit\s+reset\s+--hard\b|\bgit\s+push\b.*--force/

export function register(on) {
  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
    // Let every other command through without a question
    if (!RISKY.test(e.command)) return next(e)
    // Start from the safe answer, so a question nobody answers refuses the command
    let answer = 'Refuse'
    try {
      // The tool call waits here until the user picks one of the two labels
      answer = await $.ui.ask('Run this command? ' + e.command, ['Run it', 'Refuse'])
    } catch {
      // The user dismissed the question, or this is a claude -p run with nobody to ask
    }
    if (answer !== 'Run it') {
      // Answer without calling next, so the command doesn't run
      return { deny: 'The user declined this command. Ask before trying a different approach.' }
    }
    return next(e)
  })
}

Wenn Claude einen Befehl wie rm -rf build versucht, erscheint die Frage mit dem Befehl darin, und der Befehl wartet auf die Antwort:

  • Der Benutzer wählt Run it: Der Hook ruft next(e) auf, und die übliche Berechtigungsprüfung wird danach trotzdem ausgeführt
  • Der Benutzer wählt Refuse: Der Befehl wird nicht ausgeführt, und Claude liest den deny-Text
  • Der Benutzer gibt eine Antwort ein: $.ui.ask wird mit dem eingegebenen Text aufgelöst. Der Hook vergleicht ihn mit Run it, sodass jeder andere Text den Befehl ablehnt.
  • Niemand antwortet: $.ui.ask wird abgelehnt, wenn der Benutzer die Frage schließt oder Chat about this wählt, sowie in einer claude -p-Ausführung, sodass der catch-Block die Antwort auf Refuse belässt

Lassen Sie das Warten innerhalb eines Aufrufs der Mods-API wie $.ui.ask stattfinden, weil diese Zeit nicht auf das Zeitlimit des Hooks angerechnet wird. Zeit, die Sie mit dem Warten auf ein eigenes Promise verbringen, wird hingegen angerechnet. Claude Code überspringt einen Hook, der das Zeitlimit überschreitet, sodass der zurückgehaltene Befehl ausgeführt würde.

Einen Tool-Aufruf genehmigen oder ablehnen, bevor der Benutzer gefragt wird

Um zu entscheiden, ob ein Tool-Aufruf ausgeführt werden darf, behandeln Sie tool.check, das Event, in dem Claude Code diese Entscheidung trifft. Es wird ausgelöst, nachdem die Berechtigungsregeln und die Einstellungs-Hooks entschieden haben, und next(e) wird mit deren Entscheidung aufgelöst: allow, ask oder deny. Ihr Hook gibt diese Entscheidung oder eine andere zurück. e.input enthält die Argumente des Tools, etwa command für Bash.

Für einen festen Befehl oder Pfad verwenden Sie eine Berechtigungsregel wie Bash(npm test), die keinen Code erfordert. Behandeln Sie tool.check, wenn die Entscheidung davon abhängt, was in diesem Moment zutrifft, etwa vom aktuellen Git-Branch oder von einem Wert, den ein anderer Hook festgehalten hat.

Dieser Hook lehnt git push ab, solange der aktuelle Branch main ist:

on('tool.check', { tool: 'Bash' }, async ($, e, next) => {
  // What the permission rules and settings hooks decided: 'allow', 'ask', or 'deny'
  const decided = await next(e)
  if (!e.input.command.includes('git push')) return decided
  const branch = await $.process.run(['git', 'branch', '--show-current'])
  if (branch.stdout.trim() !== 'main') return decided
  return { decision: 'deny', reason: 'Push from a branch other than main' }
})

Auf main gibt der Hook deny zurück, selbst wenn eine Regel git push erlaubt. Auf einem anderen Branch und bei anderen Befehlen erhält der Aufruf die Entscheidung, die er ohne den Mod erhalten würde.

Der Hook prüft den Text des Befehls, betrachten Sie ihn also als Erinnerung für Claude. Um Pushes auf main für alle zu blockieren, schützen Sie den Branch bei Ihrem Git-Host.

Ein Hook kann allow, ask oder deny zurückgeben und daher auch einen Aufruf genehmigen, den ein PreToolUse-Hook außerhalb der verwalteten Einstellungen blockiert hat. Unter Berechtigungen mit Hooks erweitern ist aufgeführt, welche Entscheidungen gegenüber einem Mod Bestand haben.

Einen Prompt umschreiben oder ergänzen

Ein prompt.submit-Hook sieht jeden Prompt, bevor der Turn beginnt, und kann den Text daher umschreiben oder ergänzen. e.text ist das, was eingegeben wurde.

Um dies zu tun Geben Sie dies zurück
Den Prompt umschreiben. Die Nachricht im Transkript zeigt den neuen Text. next({ ...e, text: newText })
Text nach dem Prompt hinzufügen, den nur Claude liest next({ ...e, context: [...(e.context ?? []), extraText] })
Das Senden des Prompts verhindern { drop: 'the reason' }

Dieser Hook fügt für Claude den Namen des aktuellen Branches hinzu, sobald ein Prompt einen Pull Request erwähnt:

on('prompt.submit', async ($, e, next) => {
  // Pass on a prompt that doesn't mention a pull request as it is
  if (!/\bPR\b|pull request/i.test(e.text)) return next(e)
  const git = await $.process.run(['git', 'branch', '--show-current'])
  // Outside a git repository the command fails, so there's no branch to add
  if (git.exitCode !== 0) return next(e)
  // Keep any context an earlier hook added, and add one more line for Claude
  return next({ ...e, context: [...(e.context ?? []), 'Current branch: ' + git.stdout.trim()] })
})

Wenn Sie einen Prompt wie open a PR for this change senden, sieht Ihre Nachricht im Transkript unverändert aus, und Claude liest danach zusätzlich eine Zeile wie Current branch: feature/auth. Ein Prompt, der keinen Pull Request erwähnt, wird unverändert weitergeleitet, und git wird nicht ausgeführt.

Weitere Events decken den Rest dessen ab, was Claude liest: prompt.section für jeden Abschnitt des System-Prompts, prompt.context für den mit der ersten Nachricht gesendeten Kontext und skill.prompt für den Text eines Skills. Text aus diesen Hooks, der sich zwischen Anfragen ändert, macht den Prompt-Cache ungültig.

Einen Turn verfolgen

Ein Turn umfasst alles, was Claude als Antwort auf einen Prompt tut. Behandeln Sie turn.start, turn.step und turn.complete, um einen Turn zu verfolgen:

Event Wann es ausgelöst wird Was ein Hook tun kann
turn.start Ein Turn beginnt Beobachten. e.turnId identifiziert den Turn in den beiden anderen Events.
turn.step Claude Code ist dabei, eine Anfrage an das Modell zu senden. Ein Turn mit Tool-Aufrufen hat mehrere davon. e.agentId ist bei der Anfrage eines Subagenten gesetzt. Die Token-Nutzung jeder Anfrage auslesen, sie mit next({ ...e, model }) an ein anderes Modell senden oder antworten, ohne das Modell aufzurufen
turn.complete Der Turn wurde beendet, einschließlich eines vom Benutzer unterbrochenen Turns, bei dem e.isAborted den Wert true hat. e.answer ist Claudes endgültiger Text, e.durationMs die Dauer und e.usage die Token-Summen des Turns. Der Turn eines Subagenten löst es mit gesetztem e.agentId aus. Beobachten oder ein Objekt mit einem text-Feld zurückgeben, etwa { text: 'Done in 12 seconds' }, um eine Zeile unter der Antwort anzuzeigen

Schreiben Sie einen turn.step-Hook als asynchronen Generator, weil das Event gestreamt wird. yield* next(e) leitet die Antwort beim Streamen weiter und ergibt das fertige Ergebnis. Dieser Hook protokolliert, wie viel von jeder Anfrage die Claude API aus dem Prompt-Cache bedient hat:

// function* makes the hook a generator, which can pass the response on piece by piece
on('turn.step', async function* ($, e, next) {
  // Send the request, forward each piece as it arrives, and keep the finished result
  const result = yield* next(e)
  // Skip a result that reports no token counts
  if (result.usage) {
    $.ui.log('cache read ' + result.usage.cache_read_input_tokens + ' · wrote ' + result.usage.cache_creation_input_tokens)
  }
  // Return the result unchanged, so the turn continues as usual
  return result
})

Claudes Antwort wird wie ohne den Mod auf den Bildschirm gestreamt. Nach Abschluss jeder Anfrage gibt eine abgeblendete Zeile im Transkript die Anzahl der aus dem Cache gelesenen und der in ihn geschriebenen Token an. Ein Turn mit Tool-Aufrufen hat mehrere Anfragen und fügt daher mehrere Zeilen hinzu.

result.usage enthält die Token-Zahlen, die die Claude API für eine Anfrage meldet, sowie das model, das geantwortet hat: input_tokens, output_tokens, cache_read_input_tokens und cache_creation_input_tokens. Der Hook wird auch für Anfragen von Subagenten ausgeführt, prüfen Sie also e.agentId, wenn Sie nur die Hauptkonversation erfassen möchten.

Die Events der Einstellungs-Hooks behandeln

Einstellungs-Hooks sind die Befehls-, HTTP-, Prompt- und Agent-Hooks, die Sie in Einstellungsdateien konfigurieren. Jedes Event eines Einstellungs-Hooks, etwa Stop, SessionEnd oder PostToolUse, ist auch ein Event, das aus classic. gefolgt vom Namen des Einstellungs-Hook-Events besteht, etwa classic.Stop. e ist das JSON, das ein Einstellungs-Hook über stdin erhält, einschließlich transcript_path.

Dieser Hook verwendet Stop, das ausgelöst wird, wenn Claude mit dem Antworten fertig ist, um zu protokollieren, wo das Transkript der Sitzung gespeichert ist:

on('classic.Stop', async ($, e, next) => {
  // e has the same fields a Stop hook in a settings file reads from stdin
  $.ui.log('Transcript saved at ' + e.transcript_path)
  // Pass the event on, so Stop hooks in your settings files still run
  return next(e)
})

Jedes Mal, wenn Claude mit dem Antworten fertig ist, gibt eine abgeblendete Zeile im Transkript den Pfad der Transkriptdatei an. Der Hook gibt next(e) zurück, beobachtet das Event also nur und ändert nichts daran, wie der Turn endet.

Zusammen mit anderen Mods ausführen

Mehrere Mods können dasselbe Ereignis verarbeiten, und jeder von ihnen kann fehlschlagen. Wenn Ihr Mod Tool-Aufrufe blockiert, prüfen Sie seine Position in der Kette und was passiert, wenn sein Hook fehlschlägt.

Die Reihenfolge, in der Mods ausgeführt werden

Hooks für dasselbe Ereignis bilden eine Middleware-Kette. Das next jedes Mods ruft den Hook des nachfolgenden Mods auf, und das letzte next erreicht das eigene Verhalten von Claude Code. Der erste Mod ist der äußerste: Er sieht das Ereignis vor den anderen und das Ergebnis nach ihnen, und er entscheidet, ob die anderen überhaupt ausgeführt werden. Ein späterer Mod kann nicht verhindern, dass ein früherer ein Ereignis sieht.

Claude Code ordnet die Kette danach, woher jeder Mod stammt:

  1. Der integrierte Wächter sec-default@builtin, ein in Claude Code integrierter Mod, den /plugin als cc-plugin-sec-default auflistet, sofern er geladen wird, Mods, die Ihre Organisation in prependPlugins auflistet, und anschließend jeder andere Mod, der als Mod Ihrer Organisation gilt und nicht in appendPlugins steht
  2. Mods, die Sie installieren
  3. Mods, die Ihre Organisation in appendPlugins auflistet
  4. Andere in Claude Code integrierte Mods

Unter den Mods, die Sie installieren, wird ein Mod vor den Mods ausgeführt, die er in seinem Manifest unter dependencies auflistet. Innerhalb eines Moduls werden Hooks in der Reihenfolge ausgeführt, in der register on aufgerufen hat.

Wo Einstellungs-Hooks in der Reihenfolge ausgeführt werden

Die in Einstellungsdateien konfigurierten PreToolUse-Hooks werden ebenfalls während eines Tool-Aufrufs ausgeführt, und zwar an festen Stellen in der Kette der Mods:

  • PreToolUse-Hooks aus verwalteten Einstellungen: werden vor dem tool.call-Hook des ersten Mods ausgeführt, und eine Blockierung durch einen von ihnen ist endgültig, sodass kein Mod den Aufruf sieht.
  • PreToolUse-Hooks aus allen anderen Einstellungsdateien und aus der hooks/hooks.json von Plugins: werden ausgeführt, nachdem der letzte Mod next aufgerufen hat, als Teil des eigenen Verhaltens von Claude Code. Ein Mod, der tool.call beantwortet, ohne next aufzurufen, verhindert ihre Ausführung, und ein Mod, der next aufruft, sieht ihre Entscheidung in dem Ergebnis, das er zurückgibt.

tool.check wird ausgelöst, nachdem diese Hooks und die Berechtigungsregeln entschieden haben, sodass ein Hook darauf einen Aufruf genehmigen kann, den ein Hook aus der zweiten Gruppe blockiert hat.

Einen fehlschlagenden Hook behandeln

Ein fehlschlagender Hook unterbricht die Sitzung nicht, und Sie können entscheiden, was stattdessen passiert. Wenn ein Hook ohne .catch-Handler eine Ausnahme auslöst, eine Zeitüberschreitung hat oder ein Ergebnis mit falscher Struktur zurückgibt, hängt das weitere Vorgehen davon ab, ob er next aufgerufen hatte:

  • Er ist fehlgeschlagen, bevor er next aufgerufen hat: Claude Code überspringt ihn, und der nächste Handler wird an seiner Stelle ausgeführt
  • Er ist fehlgeschlagen, nachdem next aufgelöst wurde: Dieses Ergebnis bleibt bestehen, und nichts wird ein zweites Mal ausgeführt

Eine Zeile nennt den Mod, das Ereignis und den Grund, zum Beispiel my-mod: tool.call hook skipped: threw Error: boom. Wo Sie sie lesen, hängt von der Sitzung ab, wie Herausfinden, warum ein Mod nichts tut auflistet. Ein ui.render-Hook, dessen Zeichnung die Validierung nicht besteht, wird anders gemeldet, wie Einen Baum aus Elementen erstellen beschreibt.

Damit ein Hook, der Aufrufe blockiert, im Fehlerfall sicher blockiert (Fail-closed), fügen Sie einen .catch-Fehlerhandler hinzu, der an seiner Stelle antwortet. Hier ist guard Ihre Hook-Funktion:

// on returns a registration, and .catch attaches a handler to that one hook
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
  // next.error.kind is 'throw' or 'timeout', which says how guard failed
  return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
})

Solange guard funktioniert, wird der Handler nie ausgeführt. Wenn guard bei einem Bash-Aufruf eine Ausnahme auslöst oder eine Zeitüberschreitung hat, ruft Claude Code den Handler mit demselben Ereignis auf. Der Handler gibt { deny } zurück, sodass der Befehl nicht ausgeführt wird, und Claude liest den Text mit throw oder timeout am Ende. Ohne den Handler würde Claude Code guard überspringen und den Befehl ausführen. Der Handler hat ein eigenes, kürzeres Zeitlimit.

Nächste Schritte