# 사후 도구 사용 후크

onPostToolUse 도구가 성공적으로 실행된 후 후크가 호출됩니다. 이를 사용하여 다음을 수행합니다.

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

* 도구 결과 변환 또는 필터링
* 감사를 위한 로그 도구 실행
* 결과에 따라 컨텍스트 추가
* 대화의 결과 표시 안 함

> ```
>           **실패 변형** — `onPostToolUse` 도구가 성공적으로 실행된 경우에만 트리거됩니다. 
> ```

**failed** 도구 호출을 확인하려면 `onPostToolUseFailure`(`on_post_tool_use_failure`: Python, `OnPostToolUseFailure`: Go/.NET, `on_post_tool_use_failure`: Rust)을 등록합니다. 처리기는 `{ sessionId, toolName, toolArgs, error, timestamp, workingDirectory }`를 받습니다. 여기서 `error` 필드는 도구의 실패 결과에서 추출된 문자열이며, 모델에 추가 지침(예: 재시도 힌트)을 제공하기 위해 `{ additionalContext: string }`를 반환할 수 있습니다. 전체 목록은 [세션 후크](/ko/copilot/how-tos/copilot-sdk/hooks/hooks-overview) 을 참조하세요. <a id="failure-variant"></a>

## 후크 서명

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
import type {
  PostToolUseHookInput,
  HookInvocation,
  PostToolUseHookOutput,
} from "@github/copilot-sdk";
type PostToolUseHandler = (
  input: PostToolUseHookInput,
  invocation: HookInvocation,
) => Promise<PostToolUseHookOutput | null | undefined>;
```

```typescript
type PostToolUseHandler = (
  input: PostToolUseHookInput,
  invocation: HookInvocation,
) => Promise<PostToolUseHookOutput | null | undefined>;
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from copilot.session import PostToolUseHookInput, PostToolUseHookOutput
from typing import Callable, Awaitable

PostToolUseHandler = Callable[
    [PostToolUseHookInput, dict[str, str]],
    Awaitable[PostToolUseHookOutput | None]
]
```

```python
PostToolUseHandler = Callable[
    [PostToolUseHookInput, dict[str, str]],
    Awaitable[PostToolUseHookOutput | None]
]
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
package main

import copilot "github.com/github/copilot-sdk/go"

type PostToolUseHandler func(
    input copilot.PostToolUseHookInput,
    invocation copilot.HookInvocation,
) (*copilot.PostToolUseHookOutput, error)

func main() {}
```

```golang
type PostToolUseHandler func(
    input PostToolUseHookInput,
    invocation HookInvocation,
) (*PostToolUseHookOutput, error)
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
using GitHub.Copilot;

public delegate Task<PostToolUseHookOutput?> PostToolUseHandler(
    PostToolUseHookInput input,
    HookInvocation invocation);
```

```csharp
public delegate Task<PostToolUseHookOutput?> PostToolUseHandler(
    PostToolUseHookInput input,
    HookInvocation invocation);
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```java
import com.github.copilot.rpc.*;
import java.util.concurrent.CompletableFuture;

public class PostToolUseSignature {
    PostToolUseHandler handler = (PostToolUseHookInput input, HookInvocation invocation) ->
        CompletableFuture.completedFuture(null);
    public static void main(String[] args) {}
}
```

```java
@FunctionalInterface
public interface PostToolUseHandler {
    CompletableFuture<PostToolUseHookOutput> handle(
        PostToolUseHookInput input,
        HookInvocation invocation);
}
```

</div>

</div>

## 입력

| Field              | Type         | Description |
| ------------------ | ------------ | ----------- |
| `timestamp`        | SDK 타임스탬프 유형 | 후크가 트리거된 경우 |
| `workingDirectory` | string       | 현재 작업 디렉터리  |
| `toolName`         | string       | 호출된 도구의 이름  |
| `toolArgs`         | object       | 도구에 전달된 인수  |
| `toolResult`       | object       | 도구에서 반환된 결과 |

## 출력

결과를 변경하지 않고 `null` 또는 `undefined`을 반환하거나 그대로 전달합니다. 그렇지 않으면 다음 필드가 있는 개체를 반환합니다.

| Field               | Type    | Description               |
| ------------------- | ------- | ------------------------- |
| `modifiedResult`    | object  | 원래 대신 사용할 수정된 결과          |
| `additionalContext` | string  | 대화에 삽입된 추가 컨텍스트           |
| `suppressOutput`    | boolean | true이면 결과가 대화에 표시되지 않습니다. |

## 예제

### 모든 도구 결과 기록

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```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
    },
  },
});
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from copilot.session import PermissionHandler

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(on_permission_request=PermissionHandler.approve_all, hooks={"on_post_tool_use": on_post_tool_use})
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
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{
        OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
        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
            },
        },
    })
    _ = session
}
```

```golang
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
        },
    },
})
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
using GitHub.Copilot;

public static class PostToolUseExample
{
    public static async Task Main()
    {
        await using var client = new CopilotClient();
        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<PostToolUseHookOutput?>(null);
                },
            },
        });
    }
}
```

```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<PostToolUseHookOutput?>(null);
        },
    },
});
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

<!-- docs-validate: skip -->

```java
import com.github.copilot.*;
import com.github.copilot.rpc.*;
import java.util.concurrent.CompletableFuture;

var hooks = new SessionHooks()
    .setOnPostToolUse((input, invocation) -> {
        System.out.println("[" + invocation.getSessionId() + "] Tool: " + input.getToolName());
        System.out.println("  Args: " + input.getToolArgs());
        System.out.println("  Result: " + input.getToolResult());
        return CompletableFuture.completedFuture(null);
    });

var session = client.createSession(
    new SessionConfig()
        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
        .setHooks(hooks)
).get();
```

</div>

</div>

### 중요한 데이터 수정

```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;
    },
  },
});
```

### 대용량 결과 잘라내기

```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;
    },
  },
});
```

### 결과에 따라 컨텍스트 추가

```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;
    },
  },
});
```

### 필터 오류 스택 추적

```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;
    },
  },
});
```

### 규정 준수에 대한 감사 내역

```typescript
interface AuditEntry {
  timestamp: Date;
  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;
    },
  },
});
```

### 시끄러운 결과 표시 안 함

```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;
    },
  },
});
```

## 모범 사례

1. **변경이 필요하지 않은 경우 반환 `null`** - 빈 개체 또는 동일한 결과를 반환하는 것보다 더 효율적입니다.

2. **결과 수정에 주의** - 결과를 변경하면 모델이 도구 출력을 해석하는 방식에 영향을 줄 수 있습니다. 필요한 경우에만 수정합니다.

3. **힌트에 사용 `additionalContext`** - 결과를 수정하는 대신 모델이 해석하는 데 도움이 되는 컨텍스트를 추가합니다.

4. **로깅할 때 개인 정보 보호 고려** - 도구 결과에 중요한 데이터가 포함될 수 있습니다. 로깅 전에 수정을 적용합니다.

5. **후크를 빠르게 유지하세요** - 도구 실행 후 후크는 동기적으로 실행됩니다. 무거운 처리는 비동기적으로 수행하거나 일괄 처리해야 합니다.

## 참고하십시오

* [후크 사용](/ko/copilot/how-tos/copilot-sdk/hooks)
* [사전 도구 사용 후크](/ko/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)
* [오류 처리 후크](/ko/copilot/how-tos/copilot-sdk/hooks/error-handling)