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.
| Hook | When It's Called | Can Modify |
|---|---|---|
| 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:
- Approve, deny, or prompt for tool execution
- Modify tool arguments
- Add context for the LLM
- Suppress tool output from being shown
| Field | Type | Description |
|---|---|---|
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 |
| Field | Type | Description |
|---|---|---|
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:
- Log tool results
- Modify the result shown to the LLM
- Add additional context based on results
- Suppress output from display
| Field | Type | Description |
|---|---|---|
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 |
| Field | Type | Description |
|---|---|---|
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.
| Field | Type | Description |
|---|---|---|
prompt() |
String |
The user's prompt text |
getTimestamp() |
long |
Timestamp in milliseconds |
Return null - this hook is observation-only.
var hooks = new SessionHooks()
.setOnUserPromptSubmitted((input, invocation) -> {
System.out.println("User asked: " + input.prompt());
// Track prompts for analytics
analytics.track("user_prompt", Map.of(
"sessionId", invocation.getSessionId(),
"promptLength", input.prompt().length()
));
return CompletableFuture.completedFuture(null);
});Called when a session starts (either new or resumed).
| Field | Type | Description |
|---|---|---|
source() |
String |
"startup", "resume", or "new" |
getTimestamp() |
long |
Timestamp in milliseconds |
Return null - this hook is observation-only.
var hooks = new SessionHooks()
.setOnSessionStart((input, invocation) -> {
System.out.println("Session started: " + invocation.getSessionId());
System.out.println("Source: " + input.source());
// Initialize session-specific resources
sessionResources.put(invocation.getSessionId(), new ResourceManager());
return CompletableFuture.completedFuture(null);
});Called when a session ends.
| Field | Type | Description |
|---|---|---|
reason() |
String |
Why the session ended |
getTimestamp() |
long |
Timestamp in milliseconds |
Return null - this hook is observation-only.
var hooks = new SessionHooks()
.setOnSessionEnd((input, invocation) -> {
System.out.println("Session ended: " + input.reason());
// Clean up session resources
var resources = sessionResources.remove(invocation.getSessionId());
if (resources != null) {
resources.close();
}
return CompletableFuture.completedFuture(null);
});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:
| Method | Description |
|---|---|
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:
- Pre-tool hooks default to allowing execution
- Post-tool hooks have no effect on the result
- Lifecycle hooks are observation-only
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")
);
}
})- SessionHooks Javadoc
- PreToolUseHookInput Javadoc
- PreToolUseHookOutput Javadoc
- PostToolUseHookInput Javadoc
- PostToolUseHookOutput Javadoc
- 📖 Documentation - Core concepts, events, session management
- 📖 Advanced Usage - Tools, BYOK, MCP Servers, Custom Agents
- 📖 MCP Servers - Integrate external tools via Model Context Protocol
- 📖 API Javadoc - Complete API reference