레슨 5: 검문소에 탐침 놓기: 훅과 디버깅 워크플로
학습 목표:
- 훅이 무엇인지 설명하기: 에이전트 생명주기의 정해진 지점에서 자동 실행되는 사용자 정의 핸들러이며 세 가지 주기(세션당 한 번 / 턴당 한 번 / 루프 안의 도구 호출마다)로 묶인다는 것, 그리고 관측 가능성 요구에 맞는 이벤트를 고르고 올바른 매처를 쓰고 어떤 페이로드 필드를 쓸지 짚어내기
- 실제로 무는 함정 두 가지 피하기: 훅 서브프로세스는 하네스의 OTel 내보내기 설정을 물려받지 않는다는 것, 그리고 트랜스크립트 파일은 비동기로 쓰이므로 훅이 발화하는 시점에 최근 메시지가 없을 수 있다는 것
- 비결정성 아래에서 문제를 짚어내는 다섯 단계 워크플로 적용하기: prompt id 로 좁히기, 첫 이탈 지점 찾기, 동일 입력으로 재생하기, 같은 구성 요소를 반복해서 두들기기, 고친 뒤 실패 지점부터 복구하기
전제: 레슨 1–4 완료(비결정성과 '왜인지 말할 수 없음', 일차 증거로서의 원본 기록, 구조화 로그와 메트릭, 트레이스 트리와 텔레메트리 파이프라인의 함정) | 이전: << 레슨 4 | 다음: 레슨 6 >>
도구 실행 전후로 기록을 남기고 싶다
레슨 3은 여러분의 하네스에서 도구 호출마다 구조화 로그 항목을 쓰는 법을 가르쳤습니다. 레슨 4는 그 기록을 트레이스 트리로 꿰는 법을 가르쳤고요. 그건 여러분의 루프였고, 소스 코드가 있었고, log() 호출을 원하는 자리에 아무 데나 떨어뜨릴 수 있었습니다. 그런데 이제 프로덕션에서 Claude Code 를 돌립니다. 여러분에게는 그것의 runToolUses 가 없습니다. 원하는 것은 복잡하지 않습니다. 도구 실행 전에 한 번(어떤 파라미터를 쓰려는지), 실행 후에 한 번(무엇을 돌려받았는지) 기록하는 것. 그런데 그 삽입 지점이 남의 프로세스 안에 있습니다.
제품은 이걸 예상했습니다. 답의 이름은 훅입니다.
훅은 Claude Code 생명주기의 특정 지점에서 자동으로 실행되는 사용자 정의 셸 명령, HTTP 엔드포인트, 또는 LLM 프롬프트입니다1. 평범한 말로 하면, 설정에 "이러이러한 순간에 이 스크립트를 실행해 달라"고 선언해 두면 Claude Code 가 그 순간에 이르렀을 때 실행해 줍니다. 게다가 빈손으로 오지 않습니다. 이벤트가 발화하고 매처가 일치하면 Claude Code 가 그 이벤트에 대한 JSON 컨텍스트를 여러분의 핸들러에 넘겨줍니다1.
이 시리즈 7번째 코스에서 여러분은 하네스 루프에 승인 게이트를 썼습니다. HIGH_IMPACT 도구를 만나면 멈추고 사람의 확인을 기다린 뒤 실행하는 것이었죠. 그건 손으로 만든 개입 지점이었습니다. 훅도 같은 일을 하지만, '루프의 어느 자리가 멈춰 볼 값어치가 있는가'를 이름 붙은 목록으로 정리해 두었고 각 자리마다 고정된, 믿을 수 있는 페이로드가 있습니다. 관측 가능성 관점에서 이 목록의 가치는 '동작을 바꿀 수 있다'가 아닙니다. 동작을 바꾸지 않고 볼 수 있다는 것입니다.
세 가지 주기: 세션, 턴, 도구 호출
이벤트는 세 가지 주기로 나뉩니다1.
- 세션당 한 번:
SessionStart 와 SessionEnd
- 턴당 한 번:
UserPromptSubmit, Stop, StopFailure(이름으로 보면 StopFailure 는 턴이 정상적으로 끝나지 않은 출구에 대응합니다. 공식 인용은 주기 분류만 주었으니 정확한 의미는 여러분 버전의 레퍼런스 페이지를 보세요)
- 에이전트 루프 안의 모든 도구 호출마다:
PreToolUse 와 PostToolUse
7번째 코스의 루프 해부도와 나란히 놓으면 세 계층이 바로 맞아떨어집니다.
주기를 잘못 고르면 설명하기 어려운 숫자가 나옵니다. '작업 하나가 도구 호출을 몇 번 썼는가'를 세고 싶은데 턴 단위 검문소에 훅을 걸면 0만 얻게 됩니다. 이벤트를 고르기 전에 물으세요. 내가 세려는 그 일은 세션당 몇 번 일어나는가?
짚어 둘 세부 하나. SessionStart 는 새 세션을 열 때 발화하고 기존 세션을 재개할 때도 발화합니다1. 9번째 코스에서 --resume 을 다뤘죠. 루프 관점에서 그건 '새로 시작'이 아니지만, 그래도 SessionStart 종은 울립니다. "세션이 시작되면 새 로그를 초기화한다"고 써 두면 재개를 처음 하는 순간 이전 구간을 덮어쓰게 됩니다.
관측 가능성에 쓸모 있는 검문소 세부
PreToolUse 는 Claude 가 도구 파라미터를 만든 뒤, 도구 호출을 처리하기 전에 실행됩니다1. 이 틈이 중요합니다. 파라미터는 확정됐고(모델이 정확히 무엇을 쓰려는지 볼 수 있고) 도구는 아직 돌지 않았습니다. 레슨 2에서 실제 사례를 다뤘습니다. 팀은 Claude 가 검색 도구의 query 파라미터에 2025 를 불필요하게 덧붙여 결과를 편향시킨다는 것을 발견했죠2. 이런 종류의 버그에 대한 증거는 PreToolUse 가 볼 수 있는 파라미터 안에 있습니다.
PostToolUse 훅은 도구가 이미 성공적으로 실행된 뒤에 발화합니다. 입력에는 도구로 보낸 인자인 tool_input 과 도구가 돌려준 결과인 tool_response 가 둘 다 들어 있습니다1. 한 번의 발화가 완전한 호출 기록 하나를 줍니다. '어느 요청이 어느 응답과 짝인지'를 직접 맞춰 볼 필요가 없습니다. 레슨 2가 강조한 원칙, 완전한 왕복이 일차 증거라는 것2이 여기서는 필드 단위로 손에 쥐어집니다. 발화 조건이 '이미 성공적으로 실행됨'이라는 점에 유의하세요1. 파라미터는 생성됐지만 실행이 성공하지 못한 경우까지 덮고 싶다면 PreToolUse 와 짝지어 양쪽을 대조해야 합니다.
매처 쓰는 법: 어떤 도구든 성공적으로 완료된 뒤에 훅을 실행하려면 matcher 를 생략하거나 "*" 로 설정합니다1. 관측 가능성 시나리오가 원하는 것이 정확히 이 '훅 하나로 전부 기록' 동작입니다. 어느 도구가 깨질지 모르니 전부 기록하는 것이죠.
핸들러가 데이터를 주고받는 법: 명령 훅은 stdin 으로 JSON 데이터를 받고 종료 코드, stdout, stderr 로 결과를 전달합니다1. 그러니 최소한의 관측 가능성 핸들러는 'stdin 에서 JSON 을 읽고, 필드 몇 개를 골라 파일에 덧붙이고, 종료 코드 0 으로 끝내기'가 전부입니다. 마법은 없습니다.
이건 레슨 3의 JSON Lines 로그입니다. 다만 로그를 쓰는 주체가 '여러분의 하네스'에서 '남의 루프에 매단 작은 스크립트'로 바뀌었을 뿐입니다. 정확한 설정 구조와 필드 이름은 여러분 버전의 레퍼런스 페이지를 참고하세요.
페이로드에 소요 시간이 들어 있지만 정의를 확인하세요: 페이로드는 도구 실행 시간을 밀리초로 담은 선택적 필드를 가지고 있는데, 이 값은 권한 프롬프트와 PreToolUse 훅에서 보낸 시간을 제외합니다1. 뒤쪽 절반이 핵심입니다. 사용자가 체감하는 '이 단계를 얼마나 기다렸나'에는 확인을 누르느라 쓴 시간이 포함되지만, 이 숫자는 그것을 떼어 냅니다. '도구가 느린가'에 답하는 데 쓰면 옳고, '사용자가 얼마나 기다렸나'에 답하는 데 쓰면 체계적으로 낮게 나옵니다. 레슨 4가 다른 방식으로 같은 것을 말했습니다. 도구 스팬 아래에는 자식 스팬이 둘 있고 하나는 권한 결정을 기다린 시간, 다른 하나는 실행 자체라고요3. 이 두 구간을 섞으면 안 되기 때문에 따로 기록되는 것입니다.
훅 자신도 트레이스에 들어갑니다: 사용자 프롬프트마다 claude_code.interaction 루트 스팬이 시작되고, API 호출과 도구 호출과 훅 실행이 그 자식으로 기록됩니다3. 관측 가능성 메커니즘 자체가 관측 대상이기도 한 것입니다.
무는 경고 두 가지
첫째: 훅 서브프로세스는 OTEL_* 익스포터 변수를 물려받지 않습니다. 물려받지 않는 변수 묶음이 하나 있습니다. Claude Code 는 자신이 띄우는 모든 서브프로세스에서 OTEL_* 익스포터 변수를 제거하며, 훅도 여기 포함됩니다1.
이 문장은 아주 자연스러운 발상 하나를 곧바로 죽입니다. "하네스에 이미 엔드포인트, 프로토콜, 인증 헤더가 설정돼 있으니 훅에서 OTel SDK 를 import 하기만 하면 환경 변수가 그대로 먹겠지." 안 먹습니다. 핸들러 프로세스가 시작되는 시점에 그 변수들은 이미 제거돼 있습니다. 데이터는 내보내지지 않거나 기본 엔드포인트로 가서 사라집니다. 누가 소리쳐 경고해 주기를 기대하지 마세요. 레슨 4에서 CLI 자신의 내보내기가 조용히 실패한다는 것을 다뤘고4, 훅 안에서 여러분이 직접 만든 익스포터도 기본적으로는 더 시끄럽지 않습니다. 백엔드에 데이터가 도착하지 않을 때 양쪽 다 조용합니다.
앞으로 갈 길은 둘입니다. 훅이 자기 완결적인 내보내기 설정을 들고 가거나(엔드포인트와 인증을 스크립트에 명시적으로 쓰고 상속에 기대지 않기), 아니면 훅에서 텔레메트리를 아예 내보내지 않고 구조화 로그를 쓰게 한 뒤 백엔드에서 ID 로 텔레메트리와 맞추는 것입니다. 두 번째 길에는 공식적인 뒷받침이 있습니다. 훅 페이로드에 있는, 지금 처리 중인 사용자 프롬프트를 식별하는 UUID 가 OpenTelemetry 이벤트의 prompt.id 속성과 일치하므로 같은 프롬프트에 대한 훅 출력과 텔레메트리를 상관지을 수 있습니다1. 양쪽이 각자 쓰고 마지막에 같은 prompt id 로 조인하는 것. 레슨 4의 '상관관계 ID 로 트리에 꿰기'와 같은 수법이고, 이번엔 두 개의 데이터 소스를 가로지를 뿐입니다.
둘째: 트랜스크립트 파일은 비동기로 쓰입니다. 페이로드는 대화 JSON 의 경로를 주지만, 이 파일은 비동기로 쓰이며 메모리상의 대화보다 뒤처질 수 있어서 훅이 발화하는 시점에 현재 턴의 가장 최근 메시지가 아직 들어 있지 않을 수 있습니다1.
함정은 에러를 내지 않고 그냥 낡은 데이터를 준다는 점입니다. "페이로드 필드로는 부족하니 트랜스크립트를 직접 읽자, 거기 다 있으니까"라고 생각했다가, 간헐적으로 내용이 절반씩 빠진 기록을 얻게 됩니다. tool_input 과 tool_response 를 원한다면 페이로드 필드를 쓰세요. 그것이 PostToolUse 가 제공하겠다고 명시적으로 보장하는 것입니다1. 트랜스크립트는 나중에 되돌아볼 때 좋은 것이지, 훅이 발화하는 그 순간의 실시간 데이터 소스로 쓸 것이 아닙니다.
여러분 머신과 관련된 주의 하나. 명령 훅은 여러분의 사용자 권한 전부로 셸 명령을 실행합니다. 여러분 계정이 접근할 수 있는 어떤 파일이든 수정하고 삭제하고 접근할 수 있습니다. 설정에 추가하기 전에 모든 훅 명령을 검토하고 테스트하세요1.
훅이 제 일을 하지 않을 때
훅을 걸었는데 로그 파일이 비어 있습니다. 발화하지 않은 걸까요? 매처가 일치하지 않은 걸까요? 아니면 스크립트 자체가 죽은 걸까요? 이 시점에 여러분이 관측해야 할 대상은 관측 가능성 메커니즘 자신입니다. 레슨 4의 원칙이 여기서 다시 적용됩니다. 탐침을 먼저 검증하세요.
훅 실행 세부 — 어느 훅이 일치했는지, 종료 코드, stdout 과 stderr 전문 — 는 디버그 로그 파일에 쓰입니다1. 얻는 방법은 둘입니다. claude --debug-file <path> 로 여러분이 지정한 위치에 쓰거나, claude --debug 를 실행한 뒤 ~/.claude/debug/<session-id>.txt 를 읽는 것입니다1. 더 세밀한 훅 매칭 세부가 필요하면 CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose 를 설정해 훅 매처 개수 같은 추가 로그 줄을 볼 수 있습니다1.
디버깅 워크플로: 비결정성 아래에서 문제를 어떻게 좇는가
레슨 전반부는 탐침을 어디에 놓을지를 다뤘습니다. 후반부는 그 데이터로 실제 문제를 어떻게 좇는지를 다룹니다.
어려움부터 분명히 말해 두죠. 레슨 1에서 말했듯 에이전트는 동적으로 판단을 내리고 프롬프트가 동일해도 실행마다 비결정적입니다. 그래서 디버깅이 더 어려워집니다5. 전통적 디버깅의 첫 단계는 '재현'인데, 여기서는 그 단계가 성립하지 않습니다. 다시 돌리면 완전히 다르지만 똑같이 타당한 경로를 갈 수 있으니까요.
아래 다섯 단계는 이 코스가 직접 배열한 것이지 누군가의 공식 방법론이 아닙니다. 다만 각 단계는 출처 위에 서 있습니다.
첫째 단계: 좁히기
레슨 1–4에 걸쳐 쌓은 기록에서 먼저 범위를 '문제가 된 이 프롬프트 하나'로 좁힙니다. 레슨 4가 방법을 주었습니다. 프롬프트 하나가 촉발한 활동 전부를 추적하려면 특정 prompt.id 값으로 이벤트를 거르세요3.
이 단계의 의의는 기술적인 것이 아니라 심리적인 것입니다. '에이전트가 깨졌다'는 디버깅할 수 없는 명제이고, '이 prompt id 아래 11개 이벤트 중 하나가 어긋나 있다'는 디버깅할 수 있는 명제입니다.
둘째 단계: 첫 이탈 지점 찾기
처음부터 앞으로 읽어 가며 동작이 기대에서 벗어나기 시작하는 첫 단계를 찾습니다. 왜 '첫'을 고집할까요? 한 단계의 실패가 에이전트로 하여금 완전히 다른 궤적을 탐색하게 하고 예측할 수 없는 결과로 이어지기 때문입니다5. 끝에서 보이는 황당한 것들(존재하지 않는 파일 참조, 반복되는 에러, 먼 길 돌아가기)은 대개 하류의 잡음입니다. 8번 이벤트를 고쳐 봐야 4번 이벤트가 저지른 일을 치우는 것에 그칠 가능성이 큽니다.
'이탈'을 판정하는 데는 쓸 만한 몇 가지 모양이 있습니다2. 부르지 말았어야 할 도구를 불렀거나, 맞는 도구를 잘못된 파라미터로 불렀거나, 맞는 도구를 너무 적게 불렀거나, 도구 응답을 잘못 처리했거나. 마지막 종류가 가장 알아채기 어렵습니다. 도구 자신은 성공을 돌려줬고 로그는 전부 초록불인데, 잘못된 것은 그 성공한 결과에 대한 에이전트의 해석이기 때문입니다.
셋째 단계: 재생하며 관찰하기
공식적인 접근은 이렇습니다. 프롬프트의 효과를 이해하려고 시스템의 정확히 그 프롬프트와 도구로 시뮬레이션을 만든 다음 에이전트가 한 단계씩 일하는 것을 지켜봤습니다. 이것이 곧바로 실패 양상을 드러냈습니다. 이미 충분한 결과를 얻고도 계속하는 것, 지나치게 장황한 검색 질의를 쓰는 것, 잘못된 도구를 고르는 것 같은 것들이요5.
'곧바로 드러났다'는 대목은 생각해 볼 만합니다. 같은 버그들이 집계된 메트릭에서는 보이지 않고(성공률은 꽤 높습니다), 사후 로그에서는 한 줄씩 읽어야 눈에 띄는데, 한 단계씩 도는 것을 지켜보면 '이미 충분한데 계속 찾고 있다'를 사람 눈이 몇 초 만에 잡아냅니다. 재생할 때 통제할 것은 둘입니다. 입력이 동일해야 하고(프롬프트와 도구 정의를 바꾸지 않기), 관찰이 단계별이어야 합니다.
넷째 단계: 같은 구성 요소를 반복해서 두들기기
의심이 특정 도구로 향한다면, 몇 번 돌려 보는 정도로는 아무것도 안 나올 가능성이 큽니다. 비결정성 때문에 버그가 나타났다 사라졌다 하니까요.
팀은 도구 테스트 에이전트를 만들었습니다. 결함 있는 MCP 도구를 주면 그것을 써 보고 실패를 피하도록 도구 설명을 다시 씁니다. 수십 번 도구를 테스트하면서 이 에이전트는 핵심적인 미묘함과 버그를 찾아냈습니다5.
'수십 번'이 핵심입니다. 한 번의 실행이 숨길 수 있는 버그를 수십 번의 실행은 밀어냅니다. 경계 입력에서 이상해지는 반환 형태, 모호한 설명, 모델이 '그냥 재시도하면 되는 것'으로 오판하게 만드는 에러 메시지 같은 것들이요. 레슨 3의 진단적 읽기가 여기 연결됩니다. 중복된 도구 호출이 많다면 페이지네이션이나 토큰 제한 파라미터의 크기를 조정할 필요가 있다는 신호일 수 있고, 잘못된 파라미터로 인한 도구 에러가 많다면 도구 설명을 더 명확히 쓰거나 예시를 더 잘 줘야 한다는 신호일 수 있습니다2. 왕복을 많이 아껴 주는 것 하나. 도구 호출이 에러를 낼 때, 불투명한 에러 코드나 스택 트레이스 대신 구체적이고 실행 가능한 개선을 분명히 전달하도록 에러 응답을 프롬프트 엔지니어링할 수 있습니다2.
다섯째 단계: 고친 뒤 실패 지점부터 복구하기
고치고 나서 반사적으로 '처음부터 다시'를 누르지 마세요. 공식 입장은 직접적입니다. 에러가 발생했을 때 처음부터 다시 시작할 수는 없습니다. 재시작은 비싸고 사용자에게 짜증스럽습니다. 대신 에러가 발생한 지점에서 에이전트가 있던 곳부터 재개할 수 있는 시스템을 만들었습니다5.
9번째 코스에서 복구 메커니즘을 만드는 법을 다뤘습니다. 그 레슨의 시나리오는 '작업이 중단된 뒤 어떻게 이어받을까'였죠. 디버깅에서는 쓰임이 다릅니다. 실패 지점 이전의 수십 번의 도구 호출은 유효했고, 돈이 들었고, 옳은 결과를 냈습니다. 다시 돌려 봐야 토큰만 태우고, 새로운 비결정성을 잔뜩 끌어들여서 '이번엔 잘됐다'가 제대로 고쳐서인지 운이 좋아서인지 가릴 수 없게 만듭니다.
'재현'에 대해, 솔직한 한마디
에이전트를 재현 가능하게 만든다는 기법 묶음을 다른 데서 봤을 수 있습니다. 랜덤 시드 고정, temperature 0, 실제 도구 반환을 기록해 재생 스텁으로 쓰기 같은 것들이요.
이 관행들은 실제로 엔지니어링에 존재합니다. 8–10번째 코스의 실습이 바로 그 스텁 클라이언트 방식을 씁니다. 실제 실행 한 번의 tool_result 를 저장해 두고 이후로는 매번 같은 데이터를 반환하면 도구 쪽이 결정적이 됩니다. '내가 바꾼 그 코드 한 줄이 파싱 로직을 깼는가'를 검증하는 데 좋습니다.
다만 두 가지를 말해 둬야 합니다. 첫째, 이 기법들에는 퍼스트파티 출처의 뒷받침이 없습니다. 위 다섯 단계의 모든 단계에는 인용을 달았습니다. 이 문단에는 달지 않습니다. 정말로 없기 때문입니다. 누군가 "에이전트 문제를 재현하려면 temperature 0 을 쓰라는 것이 공식 권장"이라고 주장하거든 링크를 요구하세요.
둘째, 들리는 것보다 잠그는 범위가 좁습니다. 도구 반환을 스텁으로 만드는 것은 환경 쪽을 잠급니다. 모델 쪽은 여전히 비결정적입니다5. 그러니 '변수 둘이 움직이던 것'을 '변수 하나가 움직이는 것'으로 바꿔 줍니다. 가치 있는 일이지만, '같은 입력이면 같은 출력'류의 재현은 아닙니다. 보장으로 여기지 말고 잡음 감소로 여기세요.
어떤 문제가 이 워크플로를 쓸 값어치가 있는가
다섯 단계를 다 밟는 데는 비용이 듭니다. 좁히려면 로그를 읽어야 하고, 재생하려면 시뮬레이션을 만들어야 하고, 구성 요소를 두들기려면 수십 번을 돌려야 합니다. 거칠지만 쓸 만한 구분은 이렇습니다.
- 저빈도의 무해한 흔들림 — 한 번 검색을 한 번 더 불렀지만 결과는 여전히 맞았던 것 같은. 로그로 남기고 쌓아 두세요. 개별 사례는 조사할 값어치가 없지만, 열댓 개 모이면 공통 패턴이 보이는 경우가 많고 그 시점에 한 번 조사하는 편이 훨씬 효율적입니다.
- 고영향 — 사실과 다른 답을 줬거나, 건드리지 말았어야 할 파일을 고쳤거나, 응답 없이 멈춰 버렸거나. 빈도가 아무리 낮아도 사례를 접수하세요. 한 번만으로도 이미 충분히 비쌉니다.
- 반복 — 같은 실패 모양이 세 번째로 나타났다면 그건 운이 아니라 구조입니다. 사례를 접수하세요.
레슨 1의 문장이 여기서 판단 기준이 됩니다. 예컨대 사용자들이 에이전트는 "not finding obvious information"(뻔히 있는 정보를 찾지 못한다)이라고 제보해 오는데 왜인지는 볼 수 없었습니다. 나쁜 검색 질의를 쓴 걸까? 형편없는 출처를 고른 걸까? 도구 실패를 밟은 걸까?5 어떤 문제를 조사하지 않기로 한다는 것은 '어느 원인인지 모른다'를 받아들이는 것입니다. 무해한 흔들림이라면 괜찮고, 고영향 문제라면 도박을 하는 것입니다.
이번 레슨은 여기까지 종이 위의 이야기였습니다. 레슨 6은 양쪽 절반을 실습으로 합칩니다. 7번째 코스의 하네스에 완전한 관측 가능성 계층을 붙인 다음, '왜인지 말할 수 없다'는 증상 하나를 끝까지 좇아 내려갑니다.
정리
- 훅은 Claude Code 생명주기의 특정 지점에서 자동 실행되는 사용자 정의 명령, HTTP 엔드포인트, 또는 LLM 프롬프트다. 이벤트가 발화하고 매처가 일치하면 Claude Code 가 그 이벤트에 대한 JSON 컨텍스트를 핸들러에 넘긴다1
- 이벤트는 세 주기로 나뉜다. 세션당 한 번(
SessionStart/SessionEnd), 턴당 한 번(UserPromptSubmit/Stop/StopFailure), 루프 안의 도구 호출마다(PreToolUse/PostToolUse)1. 이벤트를 고르기 전에 주기를 확정한다. SessionStart 는 세션을 재개할 때도 발화한다1
PreToolUse 는 Claude 가 도구 파라미터를 만든 뒤 도구 호출을 처리하기 전에 실행되고, PostToolUse 는 도구가 성공적으로 실행된 뒤 발화하며 입력에 tool_input 과 tool_response 를 함께 담아 한 번의 발화로 완전한 호출 기록을 준다1. 훅 하나로 전부 기록하려면 매처를 생략하거나 "*" 로 둔다1
- 명령 훅은 stdin 으로 JSON 을 받고 종료 코드, stdout, stderr 로 응답한다1. 그 실행 밀리초 필드는 권한 프롬프트와
PreToolUse 에서 보낸 시간을 제외하므로1 사용자 대기 시간으로 쓰면 안 된다
- 부정형 결론 둘. Claude Code 는 자신이 띄우는 모든 서브프로세스에서(훅 포함)
OTEL_* 익스포터 변수를 제거하므로1 훅에서 텔레메트리를 내려면 자기 내보내기 설정을 들고 가야 한다. 페이로드가 주는 트랜스크립트 파일은 비동기로 쓰이므로 훅이 발화하는 시점에 현재 턴의 최근 메시지가 아직 없을 수 있다1. 명령 훅은 사용자 권한 전부로 실행되므로 설치 전에 검토한다1
- 훅이 동작하지 않으면
--debug-file 로 디버그 로그에서 어느 훅이 일치했는지, 종료 코드, stdout/stderr 전문을 확인하고1, 매처 개수가 필요하면 로그 레벨을 verbose 로 둔다1. 훅 페이로드의 prompt id 와 텔레메트리 이벤트의 prompt.id 는 같은 값이라 두 데이터 소스를 맞출 수 있다1
- 다섯 단계 디버깅 워크플로(이 코스의 배열):
prompt.id 로 이벤트를 걸러 이 프롬프트로 좁히기3 → 첫 이탈 지점 찾기, 한 단계의 실패가 궤적 전체를 바꾸므로5 → 동일한 프롬프트와 도구로 재생하며 단계별로 지켜보기5 → 의심 구성 요소를 수십 번 두들겨 버그를 밀어내기5 → 고친 뒤 처음부터 다시 돌리지 말고 실패 지점부터 복구하기5
- 시드 고정, temperature 0, 도구 반환 기록 재생 같은 기법에는 퍼스트파티 출처의 뒷받침이 없다. 이 코스는 엔지니어링 관행으로 제시한다. 환경 쪽은 잠그지만 모델은 여전히 비결정적이므로5 재현 보장이 아니라 잡음 감소다
- 모든 문제가 전체 워크플로를 부를 값어치가 있는 것은 아니다. 저빈도의 무해한 흔들림은 로그로 남겨 패턴을 모아 나중에 조사하고, 고영향이거나 반복되는 것은 즉시 사례로 접수한다
>> 레슨 6: 실습: 하네스에 관측 가능성 계층 붙이기
💻 연습