Session hooks allow you to intercept and modify tool execution, user prompts, and session lifecycle events. Use hooks to implement custom logic like logging, security controls, or context injection.
| Pre-Tool Use | Before a tool executes | Tool arguments, permission decision |
| Post-Tool Use | After a tool executes | Tool result, additional context |
| User Prompt Submitted | When user sends a message | Nothing (observation only) |
| Session Start | When session begins | Nothing (observation only) |
| Session End | When session ends | Nothing (observation only) |
| Checking Whether Hooks Are Registered | Before session creation | Whether any handlers are configured |
Register hooks when creating a session:
var hooks = new SessionHooks() .setOnPreToolUse((input, invocation) -> { System.out.println("Tool: " + input.getToolName()); return CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); }) .setOnPostToolUse((input, invocation) -> { System.out.println("Result: " + input.getToolResult()); return CompletableFuture.completedFuture(null); }); var session = client.createSession( new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setHooks(hooks) ).get();Called before a tool executes. Use this to:
| getToolName() | String | Name of the tool being called |
| getToolArgs() | JsonNode | Arguments passed to the tool |
| getCwd() | String | Current working directory |
| getTimestamp() | long | Timestamp in milliseconds |
| setPermissionDecision(String) | "allow", "deny", "ask" | Whether to execute the tool |
| setPermissionDecisionReason(String) | String | Reason shown to user/LLM |
| setModifiedArgs(JsonNode) | JsonNode | Modified arguments (optional) |
| setAdditionalContext(String) | String | Extra context for the LLM |
| setSuppressOutput(Boolean) | Boolean | Hide output from display |
Block dangerous tool calls:
var hooks = new SessionHooks() .setOnPreToolUse((input, invocation) -> { String tool = input.getToolName(); // Block file deletion if (tool.equals("delete_file")) { return CompletableFuture.completedFuture( PreToolUseHookOutput.deny("File deletion is not allowed") ); } // Require confirmation for shell commands if (tool.equals("run_terminal_cmd")) { return CompletableFuture.completedFuture(PreToolUseHookOutput.ask()); } // Allow everything else return CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); });Inject context into tool arguments:
var hooks = new SessionHooks() .setOnPreToolUse((input, invocation) -> { if (input.getToolName().equals("search_code")) { // Add project root to search path var mapper = new ObjectMapper(); var modifiedArgs = mapper.createObjectNode(); modifiedArgs.put("path", "/my/project/src"); modifiedArgs.set("query", input.getToolArgs().get("query")); return CompletableFuture.completedFuture( PreToolUseHookOutput.withModifiedArgs("allow", modifiedArgs) ); } return CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); });Called after a tool executes. Use this to:
| getToolName() | String | Name of the tool that was called |
| getToolArgs() | JsonNode | Arguments that were passed |
| getToolResult() | JsonNode | Result from the tool |
| getCwd() | String | Current working directory |
| getTimestamp() | long | Timestamp in milliseconds |
| setModifiedResult(String) | String | Modified result for the LLM |
| setAdditionalContext(String) | String | Extra context for the LLM |
| setSuppressOutput(Boolean) | Boolean | Hide output from display |
Log all tool executions:
var hooks = new SessionHooks() .setOnPostToolUse((input, invocation) -> { System.out.printf("[%d] %s completed%n", input.getTimestamp(), input.getToolName()); System.out.println("Result: " + input.getToolResult()); return CompletableFuture.completedFuture(null); });Add context to file read results:
var hooks = new SessionHooks() .setOnPostToolUse((input, invocation) -> { if (input.getToolName().equals("read_file")) { String context = "Note: This file was last modified 2 hours ago."; return CompletableFuture.completedFuture( new PostToolUseHookOutput(null, context, null) ); } return CompletableFuture.completedFuture(null); });Called when the user submits a prompt, before the LLM processes it. This is an observation hook - you cannot modify the prompt.
| prompt() | String | The user's prompt text |
| getTimestamp() | long | Timestamp in milliseconds |
Return null - this hook is observation-only.
Called when a session starts (either new or resumed).
| source() | String | "startup", "resume", or "new" |
| getTimestamp() | long | Timestamp in milliseconds |
Return null - this hook is observation-only.
Called when a session ends.
| reason() | String | Why the session ended |
| getTimestamp() | long | Timestamp in milliseconds |
Return null - this hook is observation-only.
Combining multiple hooks for comprehensive session control:
import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.json.PermissionHandler; import com.github.copilot.sdk.json.PreToolUseHookOutput; import com.github.copilot.sdk.json.SessionConfig; import com.github.copilot.sdk.json.SessionHooks; import java.util.concurrent.CompletableFuture; public class HooksExample { public static void main(String[] args) throws Exception { try (var client = new CopilotClient()) { client.start().get(); var hooks = new SessionHooks() // Security: control tool execution .setOnPreToolUse((input, invocation) -> { System.out.println("→ " + input.getToolName()); // Deny dangerous operations if (input.getToolName().contains("delete")) { return CompletableFuture.completedFuture( PreToolUseHookOutput.deny("Deletion not allowed") ); } return CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); }) // Logging: track tool results .setOnPostToolUse((input, invocation) -> { System.out.println("← " + input.getToolName() + " completed"); return CompletableFuture.completedFuture(null); }) // Analytics: track user prompts .setOnUserPromptSubmitted((input, invocation) -> { System.out.println("User: " + input.prompt()); return CompletableFuture.completedFuture(null); }) // Lifecycle: initialization and cleanup .setOnSessionStart((input, invocation) -> { System.out.println("Session started (" + input.source() + ")"); return CompletableFuture.completedFuture(null); }) .setOnSessionEnd((input, invocation) -> { System.out.println("Session ended: " + input.reason()); return CompletableFuture.completedFuture(null); }); var session = client.createSession( new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) .setModel("gpt-4.1") .setHooks(hooks) ).get(); var response = session.sendAndWait("List files in /tmp").get(); System.out.println(response.getData().content()); session.close(); } } }All hook handlers receive a HookInvocation object as the second parameter:
| getSessionId() | The session ID where the hook was triggered |
This allows you to correlate hooks with specific sessions when managing multiple concurrent sessions.
Use hasHooks() to quickly verify that at least one hook handler is configured:
var hooks = new SessionHooks() .setOnPreToolUse((input, invocation) -> CompletableFuture.completedFuture(PreToolUseHookOutput.allow())); if (hooks.hasHooks()) { var session = client.createSession( new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL).setHooks(hooks) ).get(); }If a hook throws an exception, the SDK logs the error and continues with default behavior:
To handle errors gracefully in your hooks:
.setOnPreToolUse((input, invocation) -> { try { // Your logic here return CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); } catch (Exception e) { logger.error("Hook error", e); // Fail-safe: deny if something goes wrong return CompletableFuture.completedFuture( PreToolUseHookOutput.deny("Internal error") ); } })© 2026