{"meta":{"title":"사후 도구 사용 후크","intro":"onPostToolUse 도구가 성공적으로 실행된 후 후크가 호출됩니다. 이를 사용하여 다음을 수행합니다.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ko/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/ko/enterprise-cloud@latest/copilot/how-tos","title":"방법"},{"href":"/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk","title":"코필로트 SDK"},{"href":"/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks","title":"후크 사용"},{"href":"/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/post-tool-use","title":"사후 도구 사용"}],"documentType":"article"},"body":"# 사후 도구 사용 후크\n\nonPostToolUse 도구가 성공적으로 실행된 후 후크가 호출됩니다. 이를 사용하여 다음을 수행합니다.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n* 도구 결과 변환 또는 필터링\n* 감사를 위한 로그 도구 실행\n* 결과에 따라 컨텍스트 추가\n* 대화의 결과 표시 안 함\n\n> ```\n>           **실패 변형** — `onPostToolUse` 도구가 성공적으로 실행된 경우에만 트리거됩니다. \n> ```\n\n**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/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/hooks-overview) 을 참조하세요. <a id=\"failure-variant\"></a>\n\n## 후크 서명\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\ntype PostToolUseHandler = (\n  input: PostToolUseHookInput,\n  invocation: HookInvocation,\n) => Promise<PostToolUseHookOutput | null | undefined>;\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nPostToolUseHandler = Callable[\n    [PostToolUseHookInput, dict[str, str]],\n    Awaitable[PostToolUseHookOutput | None]\n]\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\ntype PostToolUseHandler func(\n    input PostToolUseHookInput,\n    invocation HookInvocation,\n) (*PostToolUseHookOutput, error)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\npublic delegate Task<PostToolUseHookOutput?> PostToolUseHandler(\n    PostToolUseHookInput input,\n    HookInvocation invocation);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\n@FunctionalInterface\npublic interface PostToolUseHandler {\n    CompletableFuture<PostToolUseHookOutput> handle(\n        PostToolUseHookInput input,\n        HookInvocation invocation);\n}\n```\n\n</div>\n\n</div>\n\n## 입력\n\n| Field              | Type         | Description |\n| ------------------ | ------------ | ----------- |\n| `timestamp`        | SDK 타임스탬프 유형 | 후크가 트리거된 경우 |\n| `workingDirectory` | string       | 현재 작업 디렉터리  |\n| `toolName`         | string       | 호출된 도구의 이름  |\n| `toolArgs`         | object       | 도구에 전달된 인수  |\n| `toolResult`       | object       | 도구에서 반환된 결과 |\n\n## 출력\n\n결과를 변경하지 않고 `null` 또는 `undefined`을 반환하거나 그대로 전달합니다. 그렇지 않으면 다음 필드가 있는 개체를 반환합니다.\n\n| Field               | Type    | Description               |\n| ------------------- | ------- | ------------------------- |\n| `modifiedResult`    | object  | 원래 대신 사용할 수정된 결과          |\n| `additionalContext` | string  | 대화에 삽입된 추가 컨텍스트           |\n| `suppressOutput`    | boolean | true이면 결과가 대화에 표시되지 않습니다. |\n\n## 예제\n\n### 모든 도구 결과 기록\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input, invocation) => {\n      console.log(`[${invocation.sessionId}] Tool: ${input.toolName}`);\n      console.log(`  Args: ${JSON.stringify(input.toolArgs)}`);\n      console.log(`  Result: ${JSON.stringify(input.toolResult)}`);\n      return null; // Pass through unchanged\n    },\n  },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot.session import PermissionHandler\n\nasync def on_post_tool_use(input_data, invocation):\n    print(f\"[{invocation['session_id']}] Tool: {input_data['toolName']}\")\n    print(f\"  Args: {input_data['toolArgs']}\")\n    print(f\"  Result: {input_data['toolResult']}\")\n    return None  # Pass through unchanged\n\nsession = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={\"on_post_tool_use\": on_post_tool_use})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\nsession, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{\n    Hooks: &copilot.SessionHooks{\n        OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {\n            fmt.Printf(\"[%s] Tool: %s\\n\", inv.SessionID, input.ToolName)\n            fmt.Printf(\"  Args: %v\\n\", input.ToolArgs)\n            fmt.Printf(\"  Result: %v\\n\", input.ToolResult)\n            return nil, nil\n        },\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    Hooks = new SessionHooks\n    {\n        OnPostToolUse = (input, invocation) =>\n        {\n            Console.WriteLine($\"[{invocation.SessionId}] Tool: {input.ToolName}\");\n            Console.WriteLine($\"  Args: {input.ToolArgs}\");\n            Console.WriteLine($\"  Result: {input.ToolResult}\");\n            return Task.FromResult<PostToolUseHookOutput?>(null);\n        },\n    },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.*;\nimport com.github.copilot.rpc.*;\nimport java.util.concurrent.CompletableFuture;\n\nvar hooks = new SessionHooks()\n    .setOnPostToolUse((input, invocation) -> {\n        System.out.println(\"[\" + invocation.getSessionId() + \"] Tool: \" + input.getToolName());\n        System.out.println(\"  Args: \" + input.getToolArgs());\n        System.out.println(\"  Result: \" + input.getToolResult());\n        return CompletableFuture.completedFuture(null);\n    });\n\nvar session = client.createSession(\n    new SessionConfig()\n        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n        .setHooks(hooks)\n).get();\n```\n\n</div>\n\n</div>\n\n### 중요한 데이터 수정\n\n```typescript\nconst SENSITIVE_PATTERNS = [\n  /api[_-]?key[\"\\s:=]+[\"']?[\\w-]+[\"']?/gi,\n  /password[\"\\s:=]+[\"']?[\\w-]+[\"']?/gi,\n  /secret[\"\\s:=]+[\"']?[\\w-]+[\"']?/gi,\n];\n\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input) => {\n      if (typeof input.toolResult === \"string\") {\n        let redacted = input.toolResult;\n        for (const pattern of SENSITIVE_PATTERNS) {\n          redacted = redacted.replace(pattern, \"[REDACTED]\");\n        }\n\n        if (redacted !== input.toolResult) {\n          return { modifiedResult: redacted };\n        }\n      }\n      return null;\n    },\n  },\n});\n```\n\n### 대용량 결과 잘라내기\n\n```typescript\nconst MAX_RESULT_LENGTH = 10000;\n\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input) => {\n      const resultStr = JSON.stringify(input.toolResult);\n\n      if (resultStr.length > MAX_RESULT_LENGTH) {\n        return {\n          modifiedResult: {\n            truncated: true,\n            originalLength: resultStr.length,\n            content: resultStr.substring(0, MAX_RESULT_LENGTH) + \"...\",\n          },\n          additionalContext: `Note: Result was truncated from ${resultStr.length} to ${MAX_RESULT_LENGTH} characters.`,\n        };\n      }\n      return null;\n    },\n  },\n});\n```\n\n### 결과에 따라 컨텍스트 추가\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input) => {\n      // If a file read returned an error, add helpful context\n      if (input.toolName === \"read_file\" && input.toolResult?.error) {\n        return {\n          additionalContext:\n            \"Tip: If the file doesn't exist, consider creating it or checking the path.\",\n        };\n      }\n\n      // If shell command failed, add debugging hint\n      if (input.toolName === \"shell\" && input.toolResult?.exitCode !== 0) {\n        return {\n          additionalContext:\n            \"The command failed. Check if required dependencies are installed.\",\n        };\n      }\n\n      return null;\n    },\n  },\n});\n```\n\n### 필터 오류 스택 추적\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input) => {\n      if (input.toolResult?.error && input.toolResult?.stack) {\n        // Remove internal stack trace details\n        return {\n          modifiedResult: {\n            error: input.toolResult.error,\n            // Keep only first 3 lines of stack\n            stack: input.toolResult.stack.split(\"\\n\").slice(0, 3).join(\"\\n\"),\n          },\n        };\n      }\n      return null;\n    },\n  },\n});\n```\n\n### 규정 준수에 대한 감사 내역\n\n```typescript\ninterface AuditEntry {\n  timestamp: Date;\n  sessionId: string;\n  toolName: string;\n  args: unknown;\n  result: unknown;\n  success: boolean;\n}\n\nconst auditLog: AuditEntry[] = [];\n\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input, invocation) => {\n      auditLog.push({\n        timestamp: input.timestamp,\n        sessionId: invocation.sessionId,\n        toolName: input.toolName,\n        args: input.toolArgs,\n        result: input.toolResult,\n        success: !input.toolResult?.error,\n      });\n\n      // Optionally persist to database/file\n      await saveAuditLog(auditLog);\n\n      return null;\n    },\n  },\n});\n```\n\n### 시끄러운 결과 표시 안 함\n\n```typescript\nconst NOISY_TOOLS = [\"list_directory\", \"search_codebase\"];\n\nconst session = await client.createSession({\n  hooks: {\n    onPostToolUse: async (input) => {\n      if (NOISY_TOOLS.includes(input.toolName)) {\n        // Summarize instead of showing full result\n        const items = Array.isArray(input.toolResult)\n          ? input.toolResult\n          : input.toolResult?.items || [];\n\n        return {\n          modifiedResult: {\n            summary: `Found ${items.length} items`,\n            firstFew: items.slice(0, 5),\n          },\n        };\n      }\n      return null;\n    },\n  },\n});\n```\n\n## 모범 사례\n\n1. **변경이 필요하지 않은 경우 반환 `null`** - 빈 개체 또는 동일한 결과를 반환하는 것보다 더 효율적입니다.\n\n2. **결과 수정에 주의** - 결과를 변경하면 모델이 도구 출력을 해석하는 방식에 영향을 줄 수 있습니다. 필요한 경우에만 수정합니다.\n\n3. **힌트에 사용 `additionalContext`** - 결과를 수정하는 대신 모델이 해석하는 데 도움이 되는 컨텍스트를 추가합니다.\n\n4. **로깅할 때 개인 정보 보호 고려** - 도구 결과에 중요한 데이터가 포함될 수 있습니다. 로깅 전에 수정을 적용합니다.\n\n5. **후크를 빠르게 유지하세요** - 도구 실행 후 후크는 동기적으로 실행됩니다. 무거운 처리는 비동기적으로 수행하거나 일괄 처리해야 합니다.\n\n## 참고하십시오\n\n* [후크 사용](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks)\n* [사전 도구 사용 후크](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)\n* [오류 처리 후크](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/error-handling)"}