CLAUDE.md는 Claude Code가 .env를 수정하는 것을 막지 못합니다. Hook을 사용하세요.
요약
Claude Code 사용 시 CLAUDE.md의 프롬프트 지시만으로는 .env와 같은 중요 파일의 수정을 완벽히 막을 수 없습니다. 대신 PreToolUse 훅을 사용하여 도구 호출 단계에서 실행을 강제로 차단하는 기술적인 해결 방법을 제시합니다.
핵심 포인트
- CLAUDE.md의 텍스트 지시는 대화가 길어지면 모델의 가중치 계산에서 밀려날 수 있음
- 프롬프팅이 아닌 코드 기반의 강제성(Enforcement)이 필요함
- PreToolUse 훅을 사용하여 도구 실행 전 특정 파일 수정을 차단 가능
- 에러 메시지(stderr) 작성 시 모델이 우회하지 않도록 명확한 가이드를 제공해야 함
당신은 CLAUDE.md에 이렇게 적었습니다. 맨 윗부분에 굵은 글씨로 말이죠:
Do not modify files I did not ask you to modify.
(내가 수정하라고 요청하지 않은 파일은 수정하지 마세요.)
효과가 있었습니다. 잠시 동안은 말이죠.
그러다 세션이 길어지고, 리팩터링(refactor) 과정에서 40번째 메시지에 도달했을 때, Claude Code는 결국 .env를 수정해 버렸습니다. 혹은 migrations/ 폴더를, 아니면 당신이 한 시간 동안 공들여 설정한 설정 파일(config file)을 수정했을 수도 있습니다.
당신은 규칙을 더 크게 다시 추가합니다. NEVER(절대 안 됨). CRITICAL(중요). 느낌표 세 개까지 붙여가며 말이죠. 잠시 동안은 유지되는 듯하지만, 결국 다시 무너집니다.
왜 더 강하게 말해도 효과가 없는가
CLAUDE.md는 프롬프트(prompt)에 포함된 텍스트일 뿐입니다. 그게 전부입니다.
해당 창에 있는 다른 모든 것들도 프롬프트 내의 텍스트입니다. 20분 전에 포기한 접근 방식, 당신이 붙여넣은 에러 메시지, 읽어달라고 요청한 파일 등이 모두 포함됩니다. 대화가 길어질수록 당신의 규칙은 수천 개의 문장과 경쟁하는 단 한 줄의 문장이 되며, 대문자로 썼다고 해서 특별한 지위를 갖지는 않습니다.
따라서 모델이 명령을 거부하는 것이 아닙니다. 모델은 가중치를 계산하고 있으며, 당신의 문장은 그 계산에서 밀려난 것입니다.
이는 어떤 문구로도 해결할 수 없음을 의미합니다. 이것은 프롬프팅(prompting) 문제가 아닙니다. 강제성(enforcement)의 문제를 요청(request)만으로 해결할 수는 없습니다.
Hook은 Tool Call 이전에 실행됩니다
Claude Code는 도구(tool)를 실행하기 전에 PreToolUse 훅(hook)을 실행합니다. 이 훅은 stdin을 통해 도구 이름과 인자(arguments)를 전달받습니다. 만약 훅이 종료 코드 2로 종료되면, 해당 도구 호출(tool call)은 취소되며 stderr가 모델에게 전달됩니다.
모델이 결정하는 것이 아닙니다. 당신의 코드가 결정합니다.
의존성 없는 작동하는 버전을 소개합니다.
# .claude/guard.py
import json, sys
from pathlib import Path
...
.claude/settings.json에 다음과 같이 연결하세요:
{
"hooks": {
"PreToolUse": [
...
Claude Code를 재시작하세요. 사고가 터지길 기다리지 말고 테스트해 보세요:
echo '{"tool_name":"Edit","tool_input":{"file_path":".env"}}' | python3 .claude/guard.py
echo "exit=$?"
exit=2가 출력된다면 정상적으로 작동하는 것입니다.
실수하기 쉬운 세 가지 사항
1. stderr 메시지는 당신이 아니라 모델을 위해 작성하세요.
"Permission denied"(권한 거부)라고 적으면, 모델은 대신 cat > .env를 사용하는 Bash 명령을 시도하게 됩니다. 모델은 도움을 주려는 것입니다. 도구 실행이 실패한 것이지, 해당 동작이 금지된 것이라고 생각하지 않기 때문입니다.
다음으로 무엇을 해야 하는지 알려주세요:
Blocked: .env is protected. Do not try another path.
사용자에게 알리고 직접 편집하도록 하세요.
이 한 문장이 가드(guard)와 왱액-어-몰(whack-a-mole, 벌레 잡기 게임)의 차이를 만듭니다.
2. 닫히지 않고 열리게 하라 (Fail open, not closed).
만약 여러분의 후크(hook)가 오류를 발생시키면 프로젝트 내 모든 쓰기 작업이 중단되고, Claude Code가 고장 났다고 생각하며 한 시간을 보내게 될 것입니다.
try:
payload = json.load(sys.stdin)
except Exception:
...
오류 발생 시 모든 것을 차단하는 가드는 보호 장치가 없는 것보다 더 나쁩니다.
3. 가드 자체를 보호하라 (Protect the guard itself).
.claude/guard.py와 .claude/settings.json을 보호 목록에 추가하세요. 갇힌 모델은 반드시 자신을 차단하는 것을 '고치려고' 시도할 것이며, 그 행동은 매우 합리적일 것입니다.
이것이 다루지 않는 것들
Bash는 건드리지 않습니다. rm -rf는 아무 문제 없이 통과합니다. 만약 그것이 중요하다면, Bash 매처를 추가하고 명령어 문자열을 검사하세요. 하지만 이제 셸 파서(shell parser)를 작성하는 것이라는 점을 인지해야 하며, 이는 보이는 것보다 훨씬 큰 작업입니다.
이것은 권한 시스템(permission system)이 아니라 가드레일(guardrail)입니다. 결정적인 적대자(determined adversary)가 아닌 사고를 막습니다.
일반적인 구조
이것이 이해되면, 분리는 명확합니다:
| 위치 | 손실하는 경우 | |
|---|---|---|
CLAUDE.md | 프롬프트 내 | 컨텍스트가 커지고 우선순위가 떨어질 때 |
| 후크(hook) | 여러분의 기기 위 | 절대 — 모델의 결정이 아니기 때문 |
둘 다 사용하세요. CLAUDE.md는 왜 그래야 하는지(why)를 설명하여 모델이 협력하게 만듭니다. 후크는 '할 수 없다'(can't)가 실제로 불가능하도록 만듭니다.
정말로 잃고 싶지 않은 것은 두 번째 목록에 있어야 합니다.
더 완전한 버전은 GitHub에 올렸습니다 — glob 패턴, 스크립트 대신 편집하는 protected.txt, MultiEdit 배치 내의 모든 경로, 그리고 fail-open 처리 방식:
github.com/avenna01-ceo/claude-code-survival-kr
해당 리포지토리에는 CLAUDE.md 자체에 들어갈 규칙과 프롬프트도 있습니다. 무료이며, MIT 라이선스입니다.
만약 여러분에게 도움이 되었던 패턴(pattern)이 있다면, 정말 보고 싶습니다. 유용한 것들은 모두 먼저 시행착오를 겪은 누군가로부터 나왔기 때문입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기