SpyBara
Go Premium

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

This page contains 90 additions and 63 deletions.

2026
Thu 1 23:59 Fri 2 13:00

Mit einem Mod auf Ereignisse reagieren

Behandeln Sie Claude Code-Ereignisse von einem Mod aus: beobachten, schreiben Sie um oder beantworten Sie Tool-Aufrufe, Eingabeaufforderungen und Turns, filtern Sie, welche Ereignisse ein Hook behandelt, und planen Sie für andere Mods.

Ein Hook ist ein Ereignishandler: eine Funktion, die Claude Code ausführt, wenn ein benanntes Ereignis eintritt. Claude Code löst ein Ereignis an jedem Punkt aus, an dem es handeln wird, z. B. wenn es ein Tool ausführt, eine Eingabeaufforderung einreicht, eine Anfrage an das Modell sendet oder eine Sitzung startet oder beendet. Ihr Hook wird ausgeführt, bevor Claude Code handelt, sodass er das Ereignis beobachten, umschreiben oder an Stelle von Claude Code beantworten kann. Sie registrieren einen Hook mit on(eventName, handler).

Erstellen Sie Ihren ersten Mod, bevor Sie hier beginnen. Für jedes Ereignis und seine genauen Felder siehe die Referenz oder lesen Sie die Typen für Ihren Build.

Wie ein Hook ein Ereignis behandelt

Ein Hook sitzt zwischen einem Ereignis und dem, was Claude Code daran tun würde, sodass er das Ereignis beobachten, umschreiben oder selbst beantworten kann. Er empfängt 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, der ein Hook eines anderen Mods oder am Ende der Kette Claude Codes eigenes Verhalten ist, und wird zum Ergebnis aufgelöst. Was Ihr Hook mit next tut, entscheidet, welches der drei er tut.

Ein Ereignis beobachten

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

on('tool.call', async ($, e, next) => {
  // Wird ausgeführt, bevor das Tool ausgeführt wird
  $.ui.log('Claude is about to use ' + e.tool)
  // Geben Sie das Ereignis unverändert weiter
  return next(e)
})

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

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

on('tool.call', async ($, e, next) => {
  // Lassen Sie das Tool ausführen und warten Sie auf sein Ergebnis
  const result = await next(e)
  // Wird ausgeführt, nachdem das Tool ausgeführt wurde
  $.ui.log(e.tool + ' finished')
  // Geben Sie das Ergebnis unverändert zurück
  return result
})

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

Ein Ereignis umschreiben

Um zu ändern, worauf Claude Code handelt, z. B. den Text eines Prompts, rufen Sie next mit einer geänderten Kopie des Ereignisses auf. Das Ereignis selbst ist unveränderlich: es ist in jeder Tiefe eingefroren, und das Zuweisen zu einem Feld wirft einen Fehler. Dieser Hook schneidet jeden Prompt zu, bevor er gesendet wird:

on('prompt.submit', async ($, e, next) => {
  // Geben Sie eine Kopie des Ereignisses mit geändertem Text weiter
  return next({ ...e, text: e.text.trim() })
})

Spätere Handler und Claude Code erhalten die gekürzte Eingabeaufforderung und sehen niemals das Original. Sie können auch das Ergebnis ändern: await next(e), dann geben Sie eine Kopie des Ergebnisses mit einem ersetzten Feld zurück.

Ein Ereignis beantworten

Um ein Ereignis selbst zu behandeln, geben Sie ein Ergebnis zurück, ohne next aufzurufen. Das unterbricht die Kette, sodass spätere Mods und Claude Codes eigenes Verhalten nicht ausgeführt werden. Dieser Hook lehnt jeden Bash-Befehl ab:

on('tool.call', { tool: 'Bash' }, async () => {
  // Kein Aufruf von next, daher wird der Befehl nie ausgeführt
  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 seine eigene Ergebnisform, die die Ereignisreferenz auflistet.

Filtern Sie, welche Ereignisse ein Hook behandelt

Um einen Hook nur für einige Ereignisse auszuführen, übergeben Sie einen Filter als zweites Argument an on. Claude Code nennt den Filter einen Matcher. Es ist 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 die gleiche Funktion, hook, für einen engeren Satz von Tool-Aufrufen:

// Ein String passt zu einem Wert: nur Bash-Aufrufe
on('tool.call', { tool: 'Bash' }, hook)
// Ein Array passt zu jedem Wert darin: Edit-Aufrufe und Write-Aufrufe
on('tool.call', { tool: ['Edit', 'Write'] }, hook)
// Ein regulärer Ausdruck passt nach Muster: jedes Tool eines MCP-Servers
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 zu einem Tool, dessen Name mit mcp__github__ beginnt. Ein Aufruf zu jedem anderen Tool, z. B. Read, passt zu keinem der drei, daher wird hook nicht dafür ausgeführt.

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

Registrieren Sie jedes Ereignis einmal pro Matcher. Wenn Sie on zweimal für session.start ohne Matcher aufrufen, schlägt das Modul mit on("session.start") is registered twice without a matcher fehl. Fügen Sie alles, was Ihr Mod beim Sitzungsstart tut, in einen Hook ein.

Verfolgen, was Claude tut

Behandeln Sie diese Events, um einen Tool-Aufruf, einen Prompt oder einen Turn in dem Moment zu sehen oder zu ändern, in dem 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. Alle anderen Bash-Befehle laufen so, wie sie es ohne die Mod täten.

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 Ergebnis zurückgibt, das er erhalten hat.

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 zurückgegebene Ergebnis alles ist, was Claude über das Geschehene erfährt.

Hooks in den verwalteten Einstellungen Ihrer Organisation laufen vor dem tool.call-Hook jeder Mod, und eine Blockierung durch einen dieser Hooks ist endgültig.

Einen Tool-Aufruf anhalten, bis der Benutzer entscheidet

Ein Hook kann einen Tool-Aufruf pausieren und den Benutzer fragen, was zu tun ist, 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. Die Funktion 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 erkennt rm -r, rm -rf, git reset --hard und git push mit --force, übersieht jedoch 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 läuft danach trotzdem
  • 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 einem claude -p-Lauf, sodass der catch-Block die Antwort bei Refuse belässt

Lassen Sie die Wartezeit innerhalb eines Aufrufs der Mods-API wie $.ui.ask stattfinden, denn diese Zeit wird nicht auf das Zeitlimit des Hooks angerechnet. Zeit, die Sie mit dem Warten auf ein eigenes Promise verbringen, wird dagegen angerechnet. Claude Code überspringt einen Hook, der das Zeitlimit überschreitet, sodass der angehaltene 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, bei 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 auch ohne die Mod erhalten würde.

Der Hook gleicht den Text des Befehls ab, 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 Vorrang vor einer Mod haben.

Einen Prompt umschreiben oder ergänzen

Ein prompt.submit-Hook sieht jeden Prompt, bevor der Turn beginnt, und kann daher den Text 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 })
Nach dem Prompt Text 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, wenn 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, geht unverändert durch, 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 Kontext, der mit der ersten Nachricht gesendet wird, 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 anderen beiden Events.
turn.step Claude Code ist im Begriff, 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 lesen, sie mit next({ ...e, model }) an ein anderes Modell senden oder antworten, ohne das Modell aufzurufen
turn.complete Der Turn ist 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 unter der Antwort eine Zeile anzuzeigen

Schreiben Sie einen turn.step-Hook als asynchronen Generator, da das Event gestreamt wird. yield* next(e) leitet die Antwort während des Streamings weiter und ergibt das fertige Ergebnis. Dieser Hook protokolliert, wie viel 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 die 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 läuft auch für Anfragen von Subagenten, 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 der Einstellungs-Hooks, etwa Stop, SessionEnd oder PostToolUse, ist zugleich ein Event, dessen Name 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 eine Antwort beendet, 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 eine Antwort beendet, 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.

Führen Sie neben anderen Mods aus

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

Die Reihenfolge, in der Mods ausgeführt werden

Hooks auf dem gleichen Ereignis bilden eine Middleware-Kette. Jeder next eines Mods ruft den Hook des folgenden Mods auf, und der letzte next erreicht Claude Codes eigenes Verhalten. Der erste Mod ist am weitesten außen: 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 einen früheren nicht daran hindern, ein Ereignis zu sehen.

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

  1. Der eingebaute Guard sec-default@builtin, ein in Claude Code eingebauter Mod, den /plugin als cc-plugin-sec-default auflistet, wo er lädt, Mods, die Ihre Organisation in prependPlugins auflistet, und dann jeden anderen Mod, der als Mod Ihrer Organisation zählt und nicht in appendPlugins ist
  2. Mods, die Sie installieren
  3. Mods, die Ihre Organisation in appendPlugins auflistet
  4. Andere in Claude Code eingebaute Mods

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

Wo Settings-Hooks in der Reihenfolge ausgeführt werden

Die PreToolUse-Hooks, die in Einstellungsdateien konfiguriert sind, werden auch während eines Tool-Aufrufs an festen Punkten in der Kette von Mods ausgeführt:

  • PreToolUse-Hooks aus verwalteten Einstellungen: werden vor dem Hook tool.call des ersten Mods ausgeführt, und ein Block von einem von ihnen ist endgültig, daher sieht kein Mod den Aufruf.
  • PreToolUse-Hooks aus jeder anderen Einstellungsdatei und aus hooks/hooks.json von Plugins: werden nach dem letzten Aufruf von next eines Mods ausgeführt, als Teil von Claude Codes eigenem Verhalten. Ein Mod, der tool.call beantwortet, ohne next aufzurufen, hindert sie daran, ausgeführt zu werden, und ein Mod, der next aufruft, sieht ihre Entscheidung im Ergebnis, das er zurückgibt.

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

Behandeln Sie einen Hook, der fehlschlägt

Ein Hook, der fehlschlägt, bricht die Sitzung nicht, und Sie können entscheiden, was stattdessen passiert. Wenn ein Hook ohne .catch-Handler wirft, das Zeitlimit überschreitet oder ein Ergebnis der falschen Form zurückgibt, hängt das, was als nächstes passiert, davon ab, ob er next aufgerufen hatte:

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

Eine Zeile benennt den Mod, das Ereignis und den Grund, z. B. my-mod: tool.call hook skipped: threw Error: boom. Wo Sie es lesen, hängt von der Sitzung ab, wie Finden Sie heraus, warum ein Mod nichts tut auflistet. Ein ui.render-Hook, dessen Zeichnung nicht validiert, wird anders gemeldet, wie Erstellen Sie einen Baum aus Elementen beschreibt.

Um einen Hook, der Aufrufe blockiert, fehlgeschlagen zu schließen, fügen Sie einen .catch-Fehlerhandler hinzu, der stattdessen antwortet. Hier ist guard Ihre Hook-Funktion:

// on gibt eine Registrierung zurück, und .catch fügt einen Handler an diesen einen Hook an
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
  // next.error.kind ist 'throw' oder 'timeout', was sagt, wie guard fehlgeschlagen ist
  return { deny: 'The command guard failed, so this command was not run: ' + next.error.kind }
})

Während guard funktioniert, wird der Handler nie ausgeführt. Wenn guard bei einem Bash-Aufruf wirft oder das Zeitlimit überschreitet, ruft Claude Code den Handler mit dem gleichen Ereignis auf. Der Handler gibt { deny } zurück, daher wird der Befehl nicht ausgeführt, 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