Skip to main content
Skip to content

Arbeiten mit Haken

Haken ermöglichen es Ihnen, benutzerdefinierte Logik in jede Phase einer Copilot-Sitzung zu integrieren – von dem Moment an, in dem sie gestartet wird, bis zu jedem Benutzereingabeaufforderungs- und Toolaufruf bis zum Ende. In diesem Leitfaden werden praktische Anwendungsfälle erläutert, damit Sie Berechtigungen, Überwachung, Benachrichtigungen und vieles mehr versenden können, ohne das Verhalten des Kern-Agents zu ändern.

Übersicht

Ein Hook ist ein Callback, den Sie beim Erstellen einer Sitzung nur einmal registrieren. Das SDK ruft sie an einem gut definierten Punkt im Unterhaltungslebenszyklus auf, übergibt kontextbezogene Eingaben und akzeptiert optional die Ausgabe, die das Verhalten der Sitzung ändert.

Diagramm: Flussdiagramm mit dem beschriebenen Prozess.

HookBei AktivierungMögliche Aktionen
Session Lifecycle HooksSitzung beginnt (neu oder fortgesetzt)Kontext einfügen, Einstellungen laden
Hook für gesendete Benutzer PromptDer Benutzer sendet eine Nachricht.Umschreiben von Eingabeaufforderungen, Hinzufügen von Kontext, Filtereingaben
Hook „Benutzer-Prompt transformiert“Zur Laufzeit wird der Modell-Prompt erstellt.Überprüfen oder Ersetzen von modellbezogenen Inhalten
Pre-Tool Verwendungs-HookBevor ein Tool ausgeführt wirdZulassen/Verweigern/Ändern des Anrufs
Hook für die Verwendung des Tools nach der SitzungNach der Rückgabe eines Tools (nur bei Erfolg)Transformieren von Ergebnissen, Geheimnisse redigieren, auditieren
Hook für die Verwendung des Tools nach der SitzungNachdem ein Tool einen Fehler zurückgegeben hatHinweise für Wiederholungsversuche einfügen, Fehler protokollieren
Session Lifecycle HooksSitzung endetBereinigen, Aufzeichnen von Metriken
Fehlerbehandlung HookEin Fehler wird ausgelöst.Benutzerdefinierte Protokollierung, Wiederholungslogik, Warnungen

Alle Haken sind optional – registrieren Sie nur die, die Sie benötigen. Indem null (oder die entsprechende Sprache) von einem Hook zurückgegeben wird, wird dem SDK angezeigt, dass es mit dem Standardverhalten fortfahren soll.

Dateibasierte Repository-Haken und Repository-Hook-Einstellungen werden erst geladen, nachdem der Arbeitsbereich vertrauenswürdig ist. Benutzer-Haken bleiben verfügbar, bevor dem Arbeitsbereich vertraut wird.

Repository-Haken aus zusätzlichen Verzeichnissen

Repository-Dateisystem-Haken sind von den in diesem Handbuch beschriebenen SDK-Callbacks getrennt. Um sie aus zusätzlichen Verzeichnissen während der Erstellung lokaler Sitzungen oder der Kaltstart-Wiederaufnahme zu laden, geben Sie additionalDirectories an; CLI --add-dir nimmt ebenfalls standardmäßig teil.

DISABLE_ADDITIONAL_DIRECTORY_HOOKS=true stellt den ausschließlich primären Geltungsbereich wieder her; enableFileHooks kann Repository-Hooks durch session.rpc.options.update aktivieren oder deaktivieren; explizite SDK-Hook-Verzeichnisse und Callbacks sind unabhängig.

Discovery verwendet das Git-Stammverzeichnis jedes akzeptierten Verzeichnisses oder das Verzeichnis selbst, wenn es sich außerhalb eines Git-Repositorys befindet. Die Bereitstellung von B\packages\core innerhalb des Repositorys B ermöglicht beispielsweise sowohl B\.github\hooks als auch Bs Repository-Hook-Einstellungen, nicht nur die Konfiguration innerhalb von B\packages\core.

Warnung

Geben Sie nur vertrauenswürdige Startverzeichnisse an. Dadurch werden Hookbefehle und hook-bereitgestellte Prompts von allen berechtigten Startverzeichnissen ohne zusätzliche Genehmigung pro Verzeichnis oder pro Hook autorisiert. Später gewährte Dateizugriffe berechtigen nicht zur Verwendung von Haken. Vorhandene verwaltete Richtlinien, Hook-Disable-Steuerelemente und Sandkastenregeln gelten weiterhin; sie machen schädliche Hook-Inhalte nicht sicher.

Spätere Verzeichnisberechtigungen fügen keine Hookquellen hinzu. Ein Kaltstart muss die Verzeichnisse erneut bereitstellen; spätere Berechtigungen oder Escape-Hasch-Updates können keine beibehaltene Berechtigung ändern.

Registrieren von Haken

hooks Der Rückruf bleibt von dateibasierten hookDirectories getrennt. Informationen zu verpackten Hookkonfigurationen und angrenzenden Ressourcen finden Sie unter Ermittlungsorte für Anpassungen.

Agentbezogene deklarative Haken

Benutzerdefinierte Agents können eine hooks Ereigniszuordnung im .agent.md Frontmatter oder in der Benutzerdefinierten Agent-Konfiguration des SDK deklarieren. Hierbei handelt es sich um deklarative Befehle oder HTTP-Aktionen, nicht um SDK-Rückruffunktionen. Sie werden nur ausgeführt, während dieser Agent ausgewählt ist, zusätzlich zu den anwendbaren Sitzungs-, Benutzer-, Repository-, Plug-In- und Managed-Policy-Haken. Wenn Sie den ausgewählten Agenten ändern oder die Auswahl des ausgewählten Agenten aufheben, werden die bereichsbezogenen Haken für den nächsten Aufruf geändert, ohne andere Sitzungen oder gleichzeitige Subagenten zu ändern.

---
name: formatter
description: Formats changed files
hooks:
  PostToolUse:
    - type: command
      command: ./scripts/format-changed-files.sh
      windows: ./scripts/format-changed-files.ps1
---
Format the files you change.

Sowohl Laufzeitereignisnamen wie postToolUse als auch unterstützte VS-Code-Aliasse wie PostToolUse werden akzeptiert. command liefert den Fallback-Shellbefehl; windows, linux und osx überschreiben ihn auf den jeweiligen Plattformen. Hook-Befehle sind relativ zum Repositorystamm der ausführungssitzung, es sei denn, sie legen ein explizites Arbeitsverzeichnis fest. Hook-Nutzlasten und Entscheidungen folgen den vorhandenen Hook-Verträgen der Laufzeit.

Wenn ein lokaler Agent als Subagent ausgeführt wird, werden seine bereichsbezogenen Stop (agentStop) Aktionen bei SubagentStop statt beim normalen Turnstopp ausgeführt. Eine explizite SubagentStop Aktion für diesen Agenten beobachtet weiterhin seine eigenen untergeordneten Elemente. Stop-Callbacks erhalten stop_hook_active: true nach einem vorherigen Block. Nach acht aufeinander folgenden bereichsbezogenen Stop-Fortsetzungen ignoriert die Laufzeit weitere bereichsbezogene Blöcke und akzeptiert die endgültige Antwort des untergeordneten Prozesses, es sei denn, ein übergeordneter Lebenszyklus-Hook blockiert den Abschluss noch.

Repository- und geerbte Agent-Haken erfordern die Zulassung zum Arbeitsbereich. Die Agentermittlung oder -auswahl begründet für sich genommen kein Vertrauen. Vorhandene Hook-Disable-Einstellungen, Unternehmenseinschränkungen, Sandbox-Berechtigungen und Timeouts gelten auch für bereichsbezogene Aktionen. Vom SDK bereitgestellte Agenten können keine Privilegien für Managed Plugins erhalten. Remote- oder organisationsgehostete Agentdefinitionen führen keine bereichsbezogenen Haken aus, und der bereichsbezogene Stop-Dispatch wird nur für lokale Runtime-Subagenten, nicht für externe AHP-Teilnehmer, unterstützt.

Nicht unterstützte Ereignisnamen werden vor der Ereignisauswahl gemeldet. Aktionsvorbereitungsfehler wirken sich nur auf das jeweils deklarierte Ereignis aus; Freigegebene und verwaltete Richtlinien-Haken werden weiterhin ausgeführt.

Übergeben Sie ein hooks Objekt, wenn Sie eine Sitzung erstellen (oder fortsetzen). Jedes beispiel unten folgt diesem Muster.

Codesprachen navigation

TypeScript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient();
await client.start();

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      /* ... */
    },
    onPreToolUse: async (input, invocation) => {
      /* ... */
    },
    onPostToolUse: async (input, invocation) => {
      /* ... */
    },
    // ... add only the hooks you need
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Tipp

Jeder Hook-Handler empfängt einen invocation Parameter mit dem sessionId, der nützlich ist, um Protokolle zu korrelieren und den Zustand pro Sitzung zu verwalten.

Anwendungsfall: Berechtigungssteuerung

Dient onPreToolUse zum Erstellen einer Berechtigungsschicht, die entscheidet, welche Tools der Agent ausführen kann, welche Argumente zulässig sind und ob der Benutzer vor der Ausführung aufgefordert werden soll.

Zulassungsliste für einen sicheren Satz von Tools

Codesprachen navigation

TypeScript
const READ_ONLY_TOOLS = ["read_file", "glob", "grep", "view"];

const session = await client.createSession({
  hooks: {
    onPreToolUse: async (input) => {
      if (!READ_ONLY_TOOLS.includes(input.toolName)) {
        return {
          permissionDecision: "deny",
          permissionDecisionReason: `Only read-only tools are allowed. "${input.toolName}" was blocked.`,
        };
      }
      return { permissionDecision: "allow" };
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Einschränken des Dateizugriffs auf bestimmte Verzeichnisse

const ALLOWED_DIRS = ["/home/user/projects", "/tmp"];

const session = await client.createSession({
  hooks: {
    onPreToolUse: async (input) => {
      if (["read_file", "write_file", "edit"].includes(input.toolName)) {
        const filePath = (input.toolArgs as { path: string }).path;
        const allowed = ALLOWED_DIRS.some((dir) => filePath.startsWith(dir));

        if (!allowed) {
          return {
            permissionDecision: "deny",
            permissionDecisionReason: `Access to "${filePath}" is outside the allowed directories.`,
          };
        }
      }
      return { permissionDecision: "allow" };
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Benutzer vor destruktiven Vorgängen fragen

const DESTRUCTIVE_TOOLS = ["delete_file", "shell", "bash"];

const session = await client.createSession({
  hooks: {
    onPreToolUse: async (input) => {
      if (DESTRUCTIVE_TOOLS.includes(input.toolName)) {
        return { permissionDecision: "ask" };
      }
      return { permissionDecision: "allow" };
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Durch Rückgabe von "ask" wird die Entscheidung zur Laufzeit dem Benutzer überlassen – nützlich für destruktive Aktionen, bei denen menschliches Eingreifen erforderlich ist.

Anwendungsfall: Überwachung und Compliance

Kombinieren Sie onPreToolUse, onPostToolUseund die Sitzungslebenszyklus-Haken, um einen vollständigen Überwachungspfad zu erstellen, der jede Aktion erfasst, die der Agent ausführt.

Strukturiertes Überwachungsprotokoll

Codesprachen navigation

TypeScript
interface AuditEntry {
  timestamp: Date;
  sessionId: string;
  event: string;
  toolName?: string;
  toolArgs?: unknown;
  toolResult?: unknown;
  prompt?: string;
}

const auditLog: AuditEntry[] = [];

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      auditLog.push({
        timestamp: input.timestamp,
        sessionId: invocation.sessionId,
        event: "session_start",
      });
      return null;
    },
    onUserPromptSubmitted: async (input, invocation) => {
      auditLog.push({
        timestamp: input.timestamp,
        sessionId: invocation.sessionId,
        event: "user_prompt",
        prompt: input.prompt,
      });
      return null;
    },
    onPreToolUse: async (input, invocation) => {
      auditLog.push({
        timestamp: input.timestamp,
        sessionId: invocation.sessionId,
        event: "tool_call",
        toolName: input.toolName,
        toolArgs: input.toolArgs,
      });
      return { permissionDecision: "allow" };
    },
    onPostToolUse: async (input, invocation) => {
      auditLog.push({
        timestamp: input.timestamp,
        sessionId: invocation.sessionId,
        event: "tool_result",
        toolName: input.toolName,
        toolResult: input.toolResult,
      });
      return null;
    },
    onSessionEnd: async (input, invocation) => {
      auditLog.push({
        timestamp: input.timestamp,
        sessionId: invocation.sessionId,
        event: "session_end",
      });

      // Persist the log — swap this with your own storage backend
      await fs.promises.writeFile(
        `audit-${invocation.sessionId}.json`,
        JSON.stringify(auditLog, null, 2),
      );
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Geheime Daten aus Tool-Ergebnissen schwärzen

const SECRET_PATTERNS = [
  /(?:api[_-]?key|token|secret|password)\s*[:=]\s*["']?[\w\-\.]+["']?/gi,
];

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      if (typeof input.toolResult !== "string") return null;

      let redacted = input.toolResult;
      for (const pattern of SECRET_PATTERNS) {
        redacted = redacted.replace(pattern, "[REDACTED]");
      }

      return redacted !== input.toolResult
        ? { modifiedResult: redacted }
        : null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Anwendungsfall: Benachrichtigungen und Sounds

Haken werden im Prozess Ihrer Anwendung ausgelöst, sodass Sie alle Nebeneffekte auslösen können– Desktopbenachrichtigungen, Sounds, Slack-Nachrichten oder Webhook-Aufrufe.

Desktopbenachrichtigung für Sitzungsereignisse

Codesprachen navigation

TypeScript
import notifier from "node-notifier"; // npm install node-notifier

const session = await client.createSession({
  hooks: {
    onSessionEnd: async (input, invocation) => {
      notifier.notify({
        title: "Copilot Session Complete",
        message: `Session ${invocation.sessionId.slice(0, 8)} finished (${input.reason}).`,
      });
      return null;
    },
    onErrorOccurred: async (input) => {
      notifier.notify({
        title: "Copilot Error",
        message: input.error.slice(0, 200),
      });
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Einen Ton abspielen, wenn ein Tool fertig ist

import { exec } from "node:child_process";

const session = await client.createSession({
  hooks: {
    onPostToolUse: async (input) => {
      // macOS: play a system sound after every tool call
      exec("afplay /System/Library/Sounds/Pop.aiff");
      return null;
    },
    onErrorOccurred: async () => {
      exec("afplay /System/Library/Sounds/Basso.aiff");
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Bei Fehlern auf Slack posten

const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL!;

const session = await client.createSession({
  hooks: {
    onErrorOccurred: async (input, invocation) => {
      if (!input.recoverable) {
        await fetch(SLACK_WEBHOOK_URL, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            text: `🚨 Unrecoverable error in session \`${invocation.sessionId.slice(0, 8)}\`:\n\`\`\`${input.error}\`\`\``,
          }),
        });
      }
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Anwendungsfall: Prompt-Anreicherung

Verwenden Sie onSessionStart und onUserPromptSubmitted, um den Kontext automatisch einzufügen, damit Benutzer sich nicht wiederholen müssen.

Einfügen von Projektmetadaten beim Sitzungsstart

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input) => {
      const pkg = JSON.parse(
        await fs.promises.readFile("package.json", "utf-8"),
      );
      return {
        additionalContext: [
          `Project: ${pkg.name} v${pkg.version}`,
          `Node: ${process.version}`,
          `Working directory: ${input.workingDirectory}`,
        ].join("\n"),
      };
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Erweitern von Kurzbefehlen in Eingabeaufforderungen

const SHORTCUTS: Record<string, string> = {
  "/fix": "Find and fix all errors in the current file",
  "/test": "Write comprehensive unit tests for this code",
  "/explain": "Explain this code in detail",
  "/refactor": "Refactor this code to improve readability",
};

const session = await client.createSession({
  hooks: {
    onUserPromptSubmitted: async (input) => {
      for (const [shortcut, expansion] of Object.entries(SHORTCUTS)) {
        if (input.prompt.startsWith(shortcut)) {
          const rest = input.prompt.slice(shortcut.length).trim();
          return { modifiedPrompt: rest ? `${expansion}: ${rest}` : expansion };
        }
      }
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Anwendungsfall: Fehlerbehandlung und Wiederherstellung

Der onErrorOccurred Hook gibt Ihnen die Möglichkeit, auf Fehler zu reagieren – sei es, erneut zu versuchen, einen Menschen zu benachrichtigen oder auf ordnungsgemäße Weise herunterzufahren.

Wiederholen von vorübergehenden Modellfehlern

const session = await client.createSession({
  hooks: {
    onErrorOccurred: async (input) => {
      if (input.errorContext === "model_call" && input.recoverable) {
        return {
          errorHandling: "retry",
          retryCount: 3,
          userNotification: "Temporary model issue — retrying…",
        };
      }
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Benutzerfreundliche Fehlermeldungen

const FRIENDLY_MESSAGES: Record<string, string> = {
  model_call: "The AI model is temporarily unavailable. Please try again.",
  tool_execution: "A tool encountered an error. Check inputs and try again.",
  system: "A system error occurred. Please try again later.",
};

const session = await client.createSession({
  hooks: {
    onErrorOccurred: async (input) => {
      return {
        userNotification: FRIENDLY_MESSAGES[input.errorContext] ?? input.error,
      };
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Anwendungsfall: Sitzungsmetriken

Verfolgen Sie, wie lange Sitzungen ausgeführt werden, wie viele Tools aufgerufen werden und warum Sitzungen enden – nützlich für Dashboards und Kostenüberwachung.

Codesprachen navigation

TypeScript
const metrics = new Map<
  string,
  { start: Date; toolCalls: number; prompts: number }
>();

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input, invocation) => {
      metrics.set(invocation.sessionId, {
        start: input.timestamp,
        toolCalls: 0,
        prompts: 0,
      });
      return null;
    },
    onUserPromptSubmitted: async (_input, invocation) => {
      metrics.get(invocation.sessionId)!.prompts++;
      return null;
    },
    onPreToolUse: async (_input, invocation) => {
      metrics.get(invocation.sessionId)!.toolCalls++;
      return { permissionDecision: "allow" };
    },
    onSessionEnd: async (input, invocation) => {
      const m = metrics.get(invocation.sessionId)!;
      const durationSec =
        (input.timestamp.getTime() - m.start.getTime()) / 1000;

      console.log(
        `Session ${invocation.sessionId.slice(0, 8)}: ` +
          `${durationSec.toFixed(1)}s, ${m.prompts} prompts, ` +
          `${m.toolCalls} tool calls, ended: ${input.reason}`,
      );

      metrics.delete(invocation.sessionId);
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Kombinieren von Haken

Haken entstehen von selbst. Ein einzelnes hooks-Objekt kann Berechtigungen und Überwachung und Benachrichtigungen bearbeiten — jeder Hook erfüllt seine eigene Aufgabe.

const session = await client.createSession({
  hooks: {
    onSessionStart: async (input) => {
      console.log(`[audit] session started in ${input.workingDirectory}`);
      return { additionalContext: "Project uses TypeScript and Vitest." };
    },
    onPreToolUse: async (input) => {
      console.log(`[audit] tool requested: ${input.toolName}`);
      if (input.toolName === "shell") {
        return { permissionDecision: "ask" };
      }
      return { permissionDecision: "allow" };
    },
    onPostToolUse: async (input) => {
      console.log(`[audit] tool completed: ${input.toolName}`);
      return null;
    },
    onErrorOccurred: async (input) => {
      console.error(`[alert] ${input.errorContext}: ${input.error}`);
      return null;
    },
    onSessionEnd: async (input, invocation) => {
      console.log(
        `[audit] session ${invocation.sessionId.slice(0, 8)} ended: ${input.reason}`,
      );
      return null;
    },
  },
  onPermissionRequest: async () => ({ kind: "approve-once" }),
});

Bewährte Methoden

  1. Halten Sie Haken schnell. Jeder Hook wird inline ausgeführt – langsame Haken verzögern die Unterhaltung. Entladen Sie nach Möglichkeit schwere Arbeit (Datenbankschreibvorgänge, HTTP-Aufrufe) in eine Hintergrundwarteschlange.

  2. Kehren Sie zurück null , wenn Sie nichts ändern müssen. Dadurch wird das SDK aufgefordert, mit den Standardwerten fortzufahren und unnötige Objektzuordnungen zu vermeiden.

  3. Seien Sie mit Berechtigungsentscheidungen explizit. Die Rückgabe von { permissionDecision: "allow" } ist klarer als die von null, obwohl beide das Tool zulassen.

  4. Schlucken Sie keine kritischen Fehler. Es ist in Ordnung, wiederherstellbare Toolfehler zu unterdrücken, aber nicht wiederherstellbare immer zu protokollieren oder darauf hinzuweisen.

  5. Verwenden Sie additionalContext statt modifiedPrompt nach Möglichkeit. Durch das Anfügen des Kontexts wird die ursprüngliche Absicht des Benutzers beibehalten, während das Modell weiterhin unterstützt wird.

  6. Bereichsstatus nach Sitzungs-ID. Wenn Sie Daten pro Sitzung nachverfolgen, schlüsseln Sie diese invocation.sessionId auf und bereinigen Sie sie in onSessionEnd.

Reference

Vollständige Typdefinitionen, Eingabe-/Ausgabefeldtabellen und zusätzliche Beispiele für jeden Hook finden Sie in der API-Referenz:

Siehe auch