ClOr

ClOr

백엔드 실무 트러블슈팅과 AI 에이전트 구조 분석을 기록합니다.

Claude Code 해부학 (완결)

51만 줄 소스코드를 19편에 걸쳐 분석한 완결 시리즈

전체 시리즈 보기 →

백엔드 트러블슈팅

실무에서 겪은 장애와 해결 과정 기록

전체 시리즈 보기 →

최신 글

article thumbnail

시리즈 이전 편: [Part 6] 도구 시스템 분석 — 도구 등록부터 실행까지의 흐름을 살펴봤습니다.

목차

  • 도구 하나에 프롬프트가 500줄?
  • BashTool — 규칙의 덩어리
    • Git Safety Protocol
    • 백그라운드 태스크와 sleep 금지
    • 샌드박스 정책 주입
  • FileReadTool — 조건부 프롬프트의 정석
  • WebSearchTool — 출처 강제 규칙
  • 공통 패턴 정리
  • 그래서 뭐가 달라지나

도구 하나에 프롬프트가 500줄?

Function Calling에서 도구의 description은 보통 한두 줄입니다. "파일을 읽습니다", "검색합니다" 정도면 충분하다고 생각하기 쉽습니다.

그런데 Claude Code의 BashTool 프롬프트는 약 370줄, 렌더링 후 결과물은 500줄을 넘깁니다. 단순 설명이 아니라 git 커밋 절차, PR 생성 포맷, 샌드박스 정책, sleep 금지 규칙까지 전부 프롬프트 안에 들어 있습니다. 왜 이렇게까지 해야 할까요?

BashTool — 규칙의 덩어리

BashTool의 프롬프트 소스는 src/tools/BashTool/prompt.ts에 있습니다. 핵심은 getSimplePrompt() 함수인데, 여기서 반환하는 문자열이 모델에게 전달되는 도구 설명 전체입니다.

가장 먼저 눈에 띄는 건 도구 우선순위 지시입니다.

// src/tools/BashTool/prompt.ts
`IMPORTANT: Avoid using this tool to run ${avoidCommands} commands,
 unless explicitly instructed or after you have verified that a
 dedicated tool cannot accomplish your task.`

cat, grep, find 같은 명령어를 Bash로 실행하지 말고 전용 도구를 쓰라는 겁니다. 모델이 습관적으로 cat file.txt를 호출하면 사용자 경험이 나빠지니까요.

Git Safety Protocol

커밋 관련 규칙만 80줄이 넘습니다. getCommitAndPRInstructions() 함수가 담당하는데, 핵심 규칙을 보면 이렇습니다.

// src/tools/BashTool/prompt.ts — Git Safety Protocol
`- NEVER update the git config
- NEVER run destructive git commands (push --force, reset --hard,
  checkout ., restore ., clean -f, branch -D) unless the user
  explicitly requests these actions.
- CRITICAL: Always create NEW commits rather than amending, unless
  the user explicitly requests a git amend.`

--no-verify 금지, git add . 대신 파일명 지정, .env 커밋 경고까지 있습니다. 이 규칙들이 없으면 모델이 git push --force를 치거나, pre-commit hook 실패 후 --amend로 이전 커밋을 덮어쓰는 사고가 실제로 발생합니다.

백그라운드 태스크와 sleep 금지

// src/tools/BashTool/prompt.ts
`- Do not sleep between commands that can run immediately
- Do not retry failing commands in a sleep loop — diagnose the root cause
- If waiting for a background task you started with run_in_background,
  you will be notified when it completes — do not poll.`

모델이 sleep 10 && curl ... 같은 패턴을 만들면 사용자가 10초 동안 멍하니 기다려야 합니다. 그래서 run_in_background 파라미터를 제공하고, sleep 루프를 명시적으로 금지합니다. 환경변수로 백그라운드 기능 자체를 끌 수도 있습니다.

샌드박스 정책 주입

getSimpleSandboxSection() 함수는 런타임에 샌드박스 설정을 JSON으로 직렬화해서 프롬프트에 끼워넣습니다. 파일시스템 읽기/쓰기 허용 경로, 네트워크 허용 호스트가 전부 모델에게 전달됩니다. 모델이 허용 범위를 "알아야" 샌드박스 밖 명령을 시도하지 않으니까요.

FileReadTool — 조건부 프롬프트의 정석

src/tools/FileReadTool/prompt.ts는 상대적으로 짧지만, 동적 렌더링의 좋은 예시입니다.

// src/tools/FileReadTool/prompt.ts
`- By default, it reads up to ${MAX_LINES_TO_READ} lines`
// MAX_LINES_TO_READ = 2000

${isPDFSupported()
  ? '- This tool can read PDF files (.pdf). For large PDFs
     (more than 10 pages), you MUST provide the pages parameter'
  : ''}

MAX_LINES_TO_READ는 상수로 관리되고, PDF 지원 여부는 isPDFSupported() 함수로 런타임에 판단합니다. PDF를 지원하지 않는 환경에서는 아예 해당 문장이 프롬프트에 포함되지 않습니다. 모델에게 할 수 없는 기능을 알려주면 오히려 혼란만 생기니까요.

offsetInstruction도 두 가지 버전이 준비되어 있습니다. 기본 버전은 "전체를 읽는 걸 권장"하고, targeted 버전은 "필요한 부분만 읽으라"고 지시합니다. 호출 상황에 따라 다른 프롬프트가 들어갑니다.

WebSearchTool — 출처 강제 규칙

src/tools/WebSearchTool/prompt.ts는 50줄도 안 되지만, 가장 강한 어조의 지시가 들어 있습니다.

// src/tools/WebSearchTool/prompt.ts
`CRITICAL REQUIREMENT - You MUST follow this:
  - After answering the user's question, you MUST include a
    "Sources:" section at the end of your response
  - This is MANDATORY - never skip including sources in your response`

"CRITICAL REQUIREMENT", "MUST", "MANDATORY"를 한 문단에 세 번 씁니다. 웹 검색 결과를 출처 없이 전달하면 신뢰도 문제가 생기기 때문입니다.

날짜 주입도 인상적입니다. getLocalMonthYear()로 현재 연월을 가져와서 프롬프트에 넣습니다. 모델이 "최신 React 문서"를 검색할 때 작년 날짜를 쓰는 걸 방지하는 겁니다.

공통 패턴 정리

네 개의 도구 프롬프트를 관통하는 패턴이 있습니다.

동적 렌더링: 프롬프트가 문자열 리터럴이 아니라 함수입니다. 환경변수, 런타임 설정, 기능 플래그에 따라 내용이 달라집니다. BashTool의 샌드박스 경로, FileReadTool의 PDF 지원 여부, WebSearchTool의 현재 날짜가 전부 이 방식입니다.

조건부 포함: 지원하지 않는 기능은 프롬프트에서 아예 빠집니다. isPDFSupported()가 false면 PDF 관련 문장 자체가 없어집니다. 토큰을 아끼는 동시에 모델의 혼란을 줄입니다.

엣지 케이스 명시: "하지 마라"는 규칙이 "해라"는 규칙보다 많습니다. git add . 쓰지 마라, sleep 루프 돌리지 마라, --no-verify 붙이지 마라. 모델이 실제로 저질렀던 실수들이 규칙이 된 겁니다.

그래서 뭐가 달라지나

도구 프롬프트를 이 수준으로 작성하면, 모델의 실수 빈도가 눈에 띄게 줄어듭니다. 단순한 기능 설명 한 줄과 500줄짜리 행동 규약은 결과가 완전히 다릅니다.

Function Calling을 쓸 때 도구 description에 "이 도구는 파일을 읽습니다"만 넣고 있다면, Claude Code의 접근법을 참고해볼 만합니다. 모델이 실수한 적 있는 모든 케이스를 프롬프트에 기록하는 것 -- 그게 도구 프롬프트 엔지니어링의 핵심입니다.


다음 편 예고: [Claude Code 해부학 Part 8] 클로드 코드(Claude Code) QueryEngine 분석 — AI와 대화하는 코드의 정체

profile

ClOr

@ClOr

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

ClOr · 백엔드 트러블슈팅과 AI 에이전트 구조 분석을 기록합니다.