Claude Code에 터미널 작업을 맡기면, rm -rf와 같은 명령어를 실행하는 것이 신경 쓰입니다. Claude Code의 hooks(특정 타이밍에 사용자의 커맨드를 자동으로 실행시키는 메커니즘)를 사용하면, Bash 툴에서 명령이 실제로 실행되기 직전에 자신의 스크립트를 끼워 넣어 막을 수 있습니다. 실제 Claude Code 세션에서 테스트한 결과, rm -rf build는 막혔지만, rm -fr build는 막히지 않아 폴더가 삭제되었습니다. 설정과 테스트 결과를 정리했습니다. 다만 hooks는 문자열을 보고 막는 필터일 뿐, 샌드박스처럼 AI의 조작을 가두는 메커니즘은 아닙니다.
확인 환경
확인 조건: Claude Code 2.1.304 (npm의 @anthropic-ai/claude-code). onFailure 확인만 할 경우 2.1.295, 2.1.294도 가능 / 모델 haiku / Linux 6.12 / GNU bash 5.2.37 / jq 1.7 / 2026-10-20
일회용 폴더에서 claude -p (대화 없이 한 번만 실행하는 모드)를 실행하고, Claude에게 '이 명령어를 딱 한 번만 실행해 달라'고 요청하여 폴더가 삭제되었는지 여부로 판별했습니다. 검증 시에는 매번 권한 확인 과정에서 막히지 않도록 --dangerously-skip-permissions (권한 확인을 모두 생략하는 옵션)를 추가했습니다. 이 옵션은 일회용 폴더 검증을 위한 것이므로, 평소 작업에서는 사용하지 마십시오. 이 옵션을 적용했음에도 종료 코드 2를 반환하는 hook은 명령어를 막았습니다. 대화 모드 화면(macOS, Windows)에서는 테스트하지 않았습니다.
settings.json과 스크립트 배치
프로젝트의 .claude/settings.json에 다음을 작성합니다.
{
"hooks": {
"PreToolUse": [
...
}
args를 작성하면 셸을 거치지 않고 bash를 직접 실행합니다 (공식 문서에서는 exec form이라고 불리며, 경로에 공백이 있어도 따옴표가 필요 없습니다). onFailure는 아래 '스크립트가 작동하지 않으면 통과'에서 설명합니다.
호출할 스크립트 .claude/hooks/block-danger.sh는 다음과 같습니다. 명령어 읽기에는 jq를 사용했습니다.
#!/bin/bash
command -v jq >/dev/null || { echo 'jq가 필요합니다' >&2; exit 2; }
...
PreToolUse hook이 종료 코드 2로 끝나면, 해당 툴 호출은 실행되지 않으며, 에러 출력 메시지가 Claude에게 전달됩니다 (공식 문서 설명에서 실제로 확인했습니다).
rm -fr를 수정해도 막히지 않는다
실제 세션에서의 결과입니다.
| Claude가 요청한 명령어 | 결과 |
|---|
rm -rf build | 막힘. build는 남아있음 |
rm -fr build | 막히지 않아, build가 삭제됨 |
ls build | 통과 (막히지 않음을 확인) |
rm -rf build의 경우, Claude 측에 다음 문구가 반환되었습니다.
PreToolUse:Bash hook error: [bash ${CLAUDE_PROJECT_DIR}/.claude/hooks/block-danger.sh]: 위험한 명령어이므로 막았습니다: rm -rf build
스크립트 단독 실행(Claude Code가 전달하는 것과 같은 형태의 JSON을 표준 입력으로 넣는 방식)으로도 테스트했습니다. git push --force origin main와 git reset --hard HEAD~3는 종료 코드 2 (막음), rm -fr build와 rm -r -f build는 종료 코드 0 (통과)였습니다. 이 두 가지는 세션에서는 테스트하지 않았습니다.
문자열로 검색하는 방식이므로, 작성법을 변경한 명령어는 놓칠 수 있습니다. 스크립트 단독으로 테스트했을 때, rm -fr, rm -r -f 외에도 rm -Rf, rm --recursive --force, rm -rf (공백 2개), `
、find build -delete
、ls | xargs rm -r
、bash -c "rm -fr build"
、Python의 shutil.rmtree
도 통과했습니다. git에서도, git clean -fdx
(git이 관리하지 않는 파일을 삭제하는 명령어), git push origin +main
(+을 붙인 강제 push), git push -u origin main --force
( --force를 뒤에 배치한 형태)가 스크립트 단독으로는 통과했습니다 (세션에서는 시도하지 않았습니다). 다른 스크립트 안에서 삭제하는 경우에도 멈추지 않습니다 (이 부분은 세션에서 시도하지 않았습니다). 반대로 echo "rm -rf는 위험합니다"
처럼 문자열만 포함된 명령어는 멈춥니다.
스크립트의 command -v jq
라인은, jq가 발견되지 않을 때 멈추기 위한 라인입니다. 이 라인이 없으면, jq가 없는 환경에서는 명령어를 읽지 못한 채 종료 코드 0으로 끝나 모든 것이 통과됩니다 (종료 코드 0이므로 onFailure의 '실패'에도 해당하지 않습니다). PATH에서 jq를 제외하고 실제 세션에서 시도했을 때, 이 라인이 있는 경우 rm -rf build
도 ls build
도 "jq가 필요합니다"라는 메시지와 함께 멈추고, 이 라인이 없는 경우에는 rm -rf build
로 인해 build 폴더가 삭제되었습니다. jq가 없으면 Claude는 Bash 명령어를 단 하나도 실행할 수 없게 되므로, "jq가 필요합니다"라고 나오면 jq를 설치해 주세요.
스크립트가 작동하지 않으면 통과한다
스크립트 경로를 잘못 지정했을 때의 동작도 시도했습니다. 스크립트를 두지 않고 rm -rf build
을 요청한 결과입니다.
| 설정 | 결과 |
|---|
onFailure 없음 | 멈추지 않고, build가 삭제됨 |
| ` | |