# Post-Tool Use Hook
The `onPostToolUse` hook is called **after** a tool executes. Use it to:
- Transform or filter tool results
- Log tool execution for auditing
- Add context based on results
- Suppress results from the conversation
## Hook Signature
Node.js / TypeScript
```typescript
type PostToolUseHandler = (
input: PostToolUseHookInput,
invocation: HookInvocation
) => Promise;
```
Python
```python
PostToolUseHandler = Callable[
[PostToolUseHookInput, HookInvocation],
Awaitable[PostToolUseHookOutput | None]
]
```
Go
```go
type PostToolUseHandler func(
input PostToolUseHookInput,
invocation HookInvocation,
) (*PostToolUseHookOutput, error)
```
.NET
```csharp
public delegate Task PostToolUseHandler(
PostToolUseHookInput input,
HookInvocation invocation);
```
## Input
| Field | Type | Description |
|-------|------|-------------|
| `timestamp` | number | Unix timestamp when the hook was triggered |
| `cwd` | string | Current working directory |
| `toolName` | string | Name of the tool that was called |
| `toolArgs` | object | Arguments that were passed to the tool |
| `toolResult` | object | Result returned by the tool |
## Output
Return `null` or `undefined` to pass through the result unchanged. Otherwise, return an object with any of these fields:
| Field | Type | Description |
|-------|------|-------------|
| `modifiedResult` | object | Modified result to use instead of original |
| `additionalContext` | string | Extra context injected into the conversation |
| `suppressOutput` | boolean | If true, result won't appear in conversation |
## Examples
### Log All Tool Results
Node.js / TypeScript
```typescript
const session = await client.createSession({
hooks: {
onPostToolUse: async (input, invocation) => {
console.log(`[${invocation.sessionId}] Tool: ${input.toolName}`);
console.log(` Args: ${JSON.stringify(input.toolArgs)}`);
console.log(` Result: ${JSON.stringify(input.toolResult)}`);
return null; // Pass through unchanged
},
},
});
```
Python
```python
async def on_post_tool_use(input_data, invocation):
print(f"[{invocation['session_id']}] Tool: {input_data['toolName']}")
print(f" Args: {input_data['toolArgs']}")
print(f" Result: {input_data['toolResult']}")
return None # Pass through unchanged
session = await client.create_session({
"hooks": {"on_post_tool_use": on_post_tool_use}
})
```
Go
```go
session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
Hooks: &copilot.SessionHooks{
OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {
fmt.Printf("[%s] Tool: %s\n", inv.SessionID, input.ToolName)
fmt.Printf(" Args: %v\n", input.ToolArgs)
fmt.Printf(" Result: %v\n", input.ToolResult)
return nil, nil
},
},
})
```
.NET
```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
Hooks = new SessionHooks
{
OnPostToolUse = (input, invocation) =>
{
Console.WriteLine($"[{invocation.SessionId}] Tool: {input.ToolName}");
Console.WriteLine($" Args: {input.ToolArgs}");
Console.WriteLine($" Result: {input.ToolResult}");
return Task.FromResult(null);
},
},
});
```
### Redact Sensitive Data
```typescript
const SENSITIVE_PATTERNS = [
/api[_-]?key["\s:=]+["']?[\w-]+["']?/gi,
/password["\s:=]+["']?[\w-]+["']?/gi,
/secret["\s:=]+["']?[\w-]+["']?/gi,
];
const session = await client.createSession({
hooks: {
onPostToolUse: async (input) => {
if (typeof input.toolResult === "string") {
let redacted = input.toolResult;
for (const pattern of SENSITIVE_PATTERNS) {
redacted = redacted.replace(pattern, "[REDACTED]");
}
if (redacted !== input.toolResult) {
return { modifiedResult: redacted };
}
}
return null;
},
},
});
```
### Truncate Large Results
```typescript
const MAX_RESULT_LENGTH = 10000;
const session = await client.createSession({
hooks: {
onPostToolUse: async (input) => {
const resultStr = JSON.stringify(input.toolResult);
if (resultStr.length > MAX_RESULT_LENGTH) {
return {
modifiedResult: {
truncated: true,
originalLength: resultStr.length,
content: resultStr.substring(0, MAX_RESULT_LENGTH) + "...",
},
additionalContext: `Note: Result was truncated from ${resultStr.length} to ${MAX_RESULT_LENGTH} characters.`,
};
}
return null;
},
},
});
```
### Add Context Based on Results
```typescript
const session = await client.createSession({
hooks: {
onPostToolUse: async (input) => {
// If a file read returned an error, add helpful context
if (input.toolName === "read_file" && input.toolResult?.error) {
return {
additionalContext: "Tip: If the file doesn't exist, consider creating it or checking the path.",
};
}
// If shell command failed, add debugging hint
if (input.toolName === "shell" && input.toolResult?.exitCode !== 0) {
return {
additionalContext: "The command failed. Check if required dependencies are installed.",
};
}
return null;
},
},
});
```
### Filter Error Stack Traces
```typescript
const session = await client.createSession({
hooks: {
onPostToolUse: async (input) => {
if (input.toolResult?.error && input.toolResult?.stack) {
// Remove internal stack trace details
return {
modifiedResult: {
error: input.toolResult.error,
// Keep only first 3 lines of stack
stack: input.toolResult.stack.split("\n").slice(0, 3).join("\n"),
},
};
}
return null;
},
},
});
```
### Audit Trail for Compliance
```typescript
interface AuditEntry {
timestamp: number;
sessionId: string;
toolName: string;
args: unknown;
result: unknown;
success: boolean;
}
const auditLog: AuditEntry[] = [];
const session = await client.createSession({
hooks: {
onPostToolUse: async (input, invocation) => {
auditLog.push({
timestamp: input.timestamp,
sessionId: invocation.sessionId,
toolName: input.toolName,
args: input.toolArgs,
result: input.toolResult,
success: !input.toolResult?.error,
});
// Optionally persist to database/file
await saveAuditLog(auditLog);
return null;
},
},
});
```
### Suppress Noisy Results
```typescript
const NOISY_TOOLS = ["list_directory", "search_codebase"];
const session = await client.createSession({
hooks: {
onPostToolUse: async (input) => {
if (NOISY_TOOLS.includes(input.toolName)) {
// Summarize instead of showing full result
const items = Array.isArray(input.toolResult)
? input.toolResult
: input.toolResult?.items || [];
return {
modifiedResult: {
summary: `Found ${items.length} items`,
firstFew: items.slice(0, 5),
},
};
}
return null;
},
},
});
```
## Best Practices
1. **Return `null` when no changes needed** - This is more efficient than returning an empty object or the same result.
2. **Be careful with result modification** - Changing results can affect how the model interprets tool output. Only modify when necessary.
3. **Use `additionalContext` for hints** - Instead of modifying results, add context to help the model interpret them.
4. **Consider privacy when logging** - Tool results may contain sensitive data. Apply redaction before logging.
5. **Keep hooks fast** - Post-tool hooks run synchronously. Heavy processing should be done asynchronously or batched.
## See Also
- [Hooks Overview](./overview.md)
- [Pre-Tool Use Hook](./pre-tool-use.md)
- [Error Handling Hook](./error-handling.md)