0Pricing
Claude Architect · 강의

PostToolUse 및 발신 호출 훅

도구 결과를 가로채고 정책을 위반하는 작업을 차단합니다

PostToolUse 및 발신 호출 훅은(는) CoddyKit의 무료 Claude Architect 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Claude Architect 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Claude Architect 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Hooks Exist

Prompts steer the model, but they are probabilistic — roughly 90% reliable. For most behavior that is fine. But some actions must never slip through: issuing a large refund, deleting production data, sending money.

Hooks give you 100% deterministic enforcement. They are code that runs around tool execution, outside the model's discretion. Use hooks when a failure has financial, legal, or safety consequences.

Two Hook Points to Know

This lesson covers two enforcement points:

  • PostToolUse — fires after a tool runs and intercepts the result before the model sees it. You can trim, validate, redact, or reshape tool output.
  • Outgoing-call hooks — block a policy-violating action before it leaves your system (e.g. a refund above a threshold).

Together they bracket the dangerous part of the agentic loop: the moment a tool result enters the conversation, and the moment a side effect would escape it.

Where Hooks Sit in the Loop

Recall the agentic loop: send request, inspect stop_reason, and when it is tool_use, run the tool and append the result to history, then repeat until end_turn.

A PostToolUse hook wraps that 'run the tool' step. The model requests a tool call; your code executes it; before appending the result to the message history, the hook inspects and may modify it.

def run_tool_with_hook(tool_name, tool_input, tool_use_id):
    raw_result = execute_tool(tool_name, tool_input)
    # PostToolUse: inspect/transform BEFORE the model sees it
    safe_result = post_tool_use_hook(tool_name, raw_result)
    return {
        "type": "tool_result",
        "tool_use_id": tool_use_id,
        "content": safe_result,
    }

PostToolUse: Trim Verbose Output

A common, practical PostToolUse job is trimming tool output to the relevant fields. Raw API responses are often huge, and bloated context triggers the lost-in-the-middle problem — models attend to the start and end of context more than the middle.

The hook reshapes the result deterministically, so the model only ever sees the fields that matter.

def post_tool_use_hook(tool_name, raw_result):
    if tool_name == "lookup_order":
        # Keep only fields the model needs; drop the rest
        return {
            "order_id": raw_result["id"],
            "status": raw_result["status"],
            "total": raw_result["total"],
        }
    return raw_result

PostToolUse: Structured Errors, Not Generic Ones

PostToolUse is also where you normalize errors. A generic status like "Operation failed" blocks recovery — the model cannot tell a transient fault from a permission problem.

Reshape failures into structured errors so the model can route intelligently: a flag, a category, retryability, and context.

def post_tool_use_hook(tool_name, raw_result):
    if raw_result.get("error"):
        return {
            "isError": True,
            "errorCategory": "transient",   # transient/validation/business/permission
            "isRetryable": True,
            "message": raw_result["error"],
            "attempted_query": raw_result.get("query"),
            "partial_results": raw_result.get("partial", []),
        }
    return raw_result

Distinguish Failure from Empty

One subtle rule PostToolUse helps enforce: an access failure is not the same as a valid empty result.

  • A failed query (timeout, auth) is an error the model may retry.
  • An empty result (zero matching orders) is a legitimate answer — retrying changes nothing.

Encode the difference so the model never burns iterations retrying a query that simply has no matches.

def post_tool_use_hook(tool_name, raw_result):
    if tool_name == "lookup_order":
        if raw_result.get("connection_error"):
            return {"isError": True, "errorCategory": "transient",
                    "isRetryable": True}
        # Empty is a VALID answer, not an error
        return {"orders": raw_result.get("orders", []),
                "isError": False}
    return raw_result

Outgoing-Call Hooks: The Hard Stop

Now the headline use case. The exam's Customer Support scenario has a tool process_refund. Policy: refunds over $500 require a human.

You could write that rule in the prompt — but a prompt is ~90% reliable, and a single missed refund is a real financial loss. So you wrap the outgoing call in a hook that blocks deterministically when the threshold is exceeded. The model's judgment never gets a vote here.

REFUND_LIMIT = 500

def outgoing_call_hook(tool_name, tool_input):
    if tool_name == "process_refund" and tool_input["amount"] > REFUND_LIMIT:
        # Block the side effect; hand control back to the model
        return {
            "blocked": True,
            "reason": "Refund over $500 requires human approval.",
            "next_action": "escalate_to_human",
        }
    return execute_tool(tool_name, tool_input)

Programmatic Preconditions

Outgoing-call hooks also enforce preconditions — ordering guarantees a prompt cannot reliably provide. Example: never process a refund until get_customer has returned a verified identity.

The hook checks a fact in your own state, not the model's claim that it 'already verified'. That is the difference between a deterministic guarantee and a hopeful instruction.

def outgoing_call_hook(tool_name, tool_input, session_state):
    if tool_name == "process_refund" and not session_state.get("verified_customer_id"):
        return {
            "blocked": True,
            "reason": "Identity not verified. Call get_customer first.",
        }
    return execute_tool(tool_name, tool_input)

Feed the Block Back to the Model

Blocking is only half the job. A hook that silently swallows the action leaves the model confused and the loop stalled — that is silent suppression, an anti-pattern.

Instead, return the block as a tool result the model can read and act on. A good block message names the reason and the correct next step (here, escalate_to_human), so the agent recovers cleanly inside the same loop.

def handle_tool_call(tool_name, tool_input, tool_use_id, state):
    outcome = outgoing_call_hook(tool_name, tool_input, state)
    if isinstance(outcome, dict) and outcome.get("blocked"):
        return {"type": "tool_result", "tool_use_id": tool_use_id,
                "is_error": True, "content": outcome["reason"]}
    return {"type": "tool_result", "tool_use_id": tool_use_id,
            "content": outcome}

Hooks vs Iteration Caps

Do not confuse enforcement hooks with the loop's safety net. An iteration cap stops a runaway loop, but it is a backstop — never the primary control mechanism, and never a substitute for policy enforcement.

Hooks are the opposite: a precise, intentional guarantee on a specific action. The model still drives decisions; hooks reserve hard code only for the guarantees that genuinely matter.

When to Reach for a Hook

Decision rule for the exam and for production:

  • Use a hook when failure is financial, legal, or safety-critical, or when you need a deterministic precondition or threshold (refund > $500, verified ID before payout).
  • Use a prompt for soft guidance, tone, and the ~90% of behavior where an occasional miss is acceptable.

Hooks = deterministic. Prompts = probabilistic. Match the tool to the cost of being wrong.

Quick Check

A support agent has a process_refund tool. Company policy: refunds above $500 must be approved by a human. Most refunds are small and automated. What is the architect-grade way to enforce this?

Recap

Key takeaways:

  • PostToolUse intercepts tool results before the model sees them — trim verbose output, normalize generic failures into structured errors, and distinguish access failure from a valid empty result.
  • Outgoing-call hooks block policy-violating actions (refund > $500) and enforce preconditions (verified ID before payout).
  • Hooks = 100% deterministic; prompts = ~90% probabilistic. Use hooks when failure is financial, legal, or safety-critical.
  • Always feed a block back as a structured result — never suppress silently. Iteration caps are a safety net, not enforcement.

자주 묻는 질문

“PostToolUse 및 발신 호출 훅” 강의는 무료인가요?

네 — “PostToolUse 및 발신 호출 훅” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Claude Architect 강의 전체를 잠금 해제할 수 있습니다. Claude Architect 강의에는 총 4개의 강의가 포함되어 있습니다.

“PostToolUse 및 발신 호출 훅”에서 뭘 배우나요?

도구 결과를 가로채고 정책을 위반하는 작업을 차단합니다 브라우저에서 직접 실행하는 실습 코드로 Claude Architect을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Claude Architect을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Claude Architect은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“PostToolUse 및 발신 호출 훅” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Claude Architect 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Claude Architect 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. PostToolUse 및 발신 호출 훅
  2. 결정적 강제 적용과 프롬프트
  3. 프로그래밍 방식의 사전 조건
  4. 구조화된 인계 프로토콜
← Claude Architect(으)로 돌아가기