copilot-sdk

Session hooks

Hooks allow you to intercept and customize the behavior of Copilot sessions at key points in the conversation lifecycle. Use hooks to:

Available hooks

Hook Trigger Use Case
onPreToolUse Before a tool executes Permission control, argument validation
onPostToolUse After a tool executes (success only) Result transformation, logging
onPostToolUseFailure After a tool execution whose result was a failure Inject retry guidance, log failures
onUserPromptSubmitted When user sends a message Prompt modification, filtering
onSessionStart Session begins Add context, configure session
onSessionEnd Session ends Cleanup, analytics
onErrorOccurred Error happens Custom error handling
onAgentStop Top-level agent naturally stops Validate completion or request another turn

Quick start

Node.js / TypeScript ```typescript import { CopilotClient } from "@github/copilot-sdk"; const client = new CopilotClient(); const session = await client.createSession({ hooks: { onPreToolUse: async (input) => { console.log(`Tool called: ${input.toolName}`); // Allow all tools return { permissionDecision: "allow" }; }, onPostToolUse: async (input) => { console.log(`Tool result: ${JSON.stringify(input.toolResult)}`); return null; // No modifications }, onSessionStart: async (input) => { return { additionalContext: "User prefers concise answers." }; }, }, }); ```
Python ```python from copilot import CopilotClient from copilot.session import PermissionHandler async def main(): client = CopilotClient() await client.start() async def on_pre_tool_use(input_data, invocation): print(f"Tool called: {input_data['toolName']}") return {"permissionDecision": "allow"} async def on_post_tool_use(input_data, invocation): print(f"Tool result: {input_data['toolResult']}") return None async def on_session_start(input_data, invocation): return {"additionalContext": "User prefers concise answers."} session = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={ "on_pre_tool_use": on_pre_tool_use, "on_post_tool_use": on_post_tool_use, "on_session_start": on_session_start, }) ```
Go ```go package main import ( "context" "fmt" copilot "github.com/github/copilot-sdk/go" ) func main() { client := copilot.NewClient(nil) session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ Hooks: &copilot.SessionHooks{ OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { fmt.Printf("Tool called: %s\n", input.ToolName) return &copilot.PreToolUseHookOutput{ PermissionDecision: "allow", }, nil }, OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) { fmt.Printf("Tool result: %v\n", input.ToolResult) return nil, nil }, OnSessionStart: func(input copilot.SessionStartHookInput, inv copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) { return &copilot.SessionStartHookOutput{ AdditionalContext: "User prefers concise answers.", }, nil }, }, }) _ = session } ```
.NET ```csharp using GitHub.Copilot; var client = new CopilotClient(); var session = await client.CreateSessionAsync(new SessionConfig { Hooks = new SessionHooks { OnPreToolUse = (input, invocation) => { Console.WriteLine($"Tool called: {input.ToolName}"); return Task.FromResult<PreToolUseHookOutput?>( new PreToolUseHookOutput { PermissionDecision = "allow" } ); }, OnPostToolUse = (input, invocation) => { Console.WriteLine($"Tool result: {input.ToolResult}"); return Task.FromResult<PostToolUseHookOutput?>(null); }, OnSessionStart = (input, invocation) => { return Task.FromResult<SessionStartHookOutput?>( new SessionStartHookOutput { AdditionalContext = "User prefers concise answers." } ); }, }, }); ```
Java ```java import com.github.copilot.*; import com.github.copilot.rpc.*; import java.util.concurrent.CompletableFuture; try (var client = new CopilotClient()) { client.start().get(); var hooks = new SessionHooks() .setOnPreToolUse((input, invocation) -> { System.out.println("Tool called: " + input.getToolName()); return CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); }) .setOnPostToolUse((input, invocation) -> { System.out.println("Tool result: " + input.getToolResult()); return CompletableFuture.completedFuture(null); }) .setOnSessionStart((input, invocation) -> { return CompletableFuture.completedFuture( new SessionStartHookOutput("User prefers concise answers.", null) ); }); var session = client.createSession( new SessionConfig() .setHooks(hooks) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) ).get(); } ```

Hook invocation context

Every hook receives an invocation parameter with context about the current session:

Field Type Description
sessionId string The ID of the current session

This allows hooks to maintain state or perform session-specific logic.

Common patterns

Logging all tool calls

const session = await client.createSession({
  hooks: {
    onPreToolUse: async (input) => {
      console.log(`[${new Date().toISOString()}] Tool: ${input.toolName}, Args: ${JSON.stringify(input.toolArgs)}`);
      return { permissionDecision: "allow" };
    },
    onPostToolUse: async (input) => {
      console.log(`[${new Date().toISOString()}] Result: ${JSON.stringify(input.toolResult)}`);
      return null;
    },
  },
});

Blocking dangerous tools

const BLOCKED_TOOLS = ["shell", "bash", "exec"];

const session = await client.createSession({
  hooks: {
    onPreToolUse: async (input) => {
      if (BLOCKED_TOOLS.includes(input.toolName)) {
        return {
          permissionDecision: "deny",
          permissionDecisionReason: "Shell access is not permitted",
        };
      }
      return { permissionDecision: "allow" };
    },
  },
});

Adding user context

const session = await client.createSession({
  hooks: {
    onSessionStart: async () => {
      const userPrefs = await loadUserPreferences();
      return {
        additionalContext: `User preferences: ${JSON.stringify(userPrefs)}`,
      };
    },
  },
});

Hook guides

See also