본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 15. 06:58

【후편】 Claude Code의 폭주를 막는 법 ── 「부탁(CLAUDE.md)」과 「물리적인 벽(settings.json)」의 이중 방어 체계

요약

Claude Code의 자율적인 동작을 제어하기 위해 CLAUDE.md와 settings.json을 활용한 이중 방어 체계 구축 방법을 소개합니다. AI에게 스스로를 구속할 규칙을 설계하게 하여 실용적인 개발 플로우를 만드는 노하우를 다룹니다.

핵심 포인트

  • CLAUDE.md 상단에 필수 확인 사항을 배치하여 우선순위 확보
  • 설계 승인 후 구현하는 3단계 개발 플로우 적용
  • 증거(Log, Diff 등) 기반의 답변을 유도하여 AI의 환각 방지
  • 멱등성 확보를 위한 중복 체크 규칙 등 구체적 제약 사항 설정
  • settings.json을 통한 물리적 제약으로 AI의 폭주 차단

지방에서 독립계 시스템 아키텍트(System Architect)로 활동하고 있는 다이콘(H.I. MET Architect)입니다. 부업으로 업무 통합 기반인 「Weaver」(Redmine + Mattermost + Docker + Cloudflare Tunnel 구성)를 개발하고 있습니다.

[전편]에서는 「AI에게 전달할 규칙을 CLAUDE.md에 작성한다」는 이야기를 했습니다.

하지만, 여기에는 솔직하게 말해야 할 것이 있습니다.

CLAUDE.md는 "지시"일 뿐이며, 강제력은 없습니다.

후편은 「CLAUDE.md만으로는 막을 수 없는 조작을 settings.json으로 물리적으로 차단하는」 이야기입니다.

그리고 이번에는 조금 메타(Meta)한 이야기이기도 합니다. 이 기사에서 소개하는 설정 파일은, Claude Code 자신과 상담하며 최종 확정한 것입니다. 즉, "AI에게 자신을 구속할 규칙을 설계하게 했다"는 뜻입니다. AI에게 "당신이 폭주하지 않기 위해서는 어떤 제약이 필요합니까?"라고 묻는 것. 꽤나 초현실적인 질문이지만, 돌아온 대답은 매우 실용적이었습니다.

실제 CLAUDE.md에 무엇을 적고 있는가

Weaver의 Redmine 프로젝트에는 다음과 같은 구성의 CLAUDE.md가 놓여 있습니다.

세션 시작 시 필수 확인 사항 (서두에 배치)

## 세션 시작 시 필수 확인 사항
- 작업 시작 전에 `MEMORY.md`와 `ERRORS.md`를 반드시 읽을 것.
- "여기까지"라고 말하면, 현재 상태와 다음 단계를 `MEMORY.md`에 추가할 것.

이 블록을 파일의 맨 위에 두는 것이 포인트입니다. Claude Code는 파일의 처음부터 읽기 때문에, 가장 먼저 눈에 들어오는 것이 우선적으로 적용됩니다.

3단계 개발 플로우 (이것이 핵심)

### Phase 1 — 설계 제시 및 승인
요구를 받으면 즉시 코드를 작성하지 말고, 다음을 제시하여 사용자의 승인을 얻는다.
1. 구현 접근 방식: 어떤 로직으로 구현 및 수정할 것인가
...

Phase 3의 "증거 기반"이 특히 효과적입니다.

AI의 "완벽합니다!"는 매우 유창합니다. 너무 유창해서 처음에는 한동안 믿었습니다. "했다"와 "했는지 확인했다"는 전혀 다릅니다. 사람으로 치면, "자료 이미 보냈습니다"라고 말하면서 초안 폴더에 넣어둔 상태와 같습니다. 이 체크리스트를 배치한 이후로는 로그(Log), 차이점(Diff), 쿼리(Query) 결과라는 "물증"을 곁들여 답변하게 되었습니다.

"멱등성(Idempotency)이 담보되어 있는가"도 현실적으로 와닿았던 한 줄입니다.

Mattermost 데모 사용자 투입 시, 동일한 스크립트를 2번 실행했더니 19명이 38명이 되어 있었다——아무도 채용했다고 기억하지 않는데 인원이 배로 늘어났다. 꿈 같은 이야기지만, 그것대로 곤란한 상황입니다. 그런 사고를 방지하기 위해, 사용자 생성 전에 username으로 중복 체크를 하는 규칙도 개별적으로 추가하고 있습니다.

Phase 1의 출력은 어떤 형태인가 —— 실제 사례

"Phase 1에서 설계안을 내고 기다린다"라고 적었지만, 실제로 어떤 형태로 돌아오는지 이미지로 떠올리기 어려울 수도 있습니다.

Weaver의 Redmine–Mattermost SSO 통합 태스크에서 돌아온 Phase 1의 출력을 그대로 올립니다. 구동 직후 Claude Code가 자율적으로 파일을 읽고, DB를 조사하여, 두 가지 구현 접근 방식을 트레이드오프(Trade-off)와 함께 제시한 답변입니다 (일부 마스킹 처리됨).

Phase 1 실례: Redmine–Mattermost SSO 통합 조사 보고 (클릭하여 확장)

Read 1 file, ran 12 shell commands
Read ERRORS.md (30 lines)
Read MEMORY.md (69 lines)
...

조사 결과를 정리합니다. VPS 환경에서 「Redmine 로그인 기점 → 💬 채팅 버튼 → MM 자동 SSO 로그인」을 실현하기 위한 전제 조건이 생각보다 잘 갖춰져 있다는 것을 알게 되었습니다.

조사로 판명된 중요 사실

  • VPS Mattermost의 SiteURL은 https://mattermost.met-architect.com (docker-compose.yml의 환경 변수로 설정. config.json상의 값 http://localhost:8080

는 실행 시 덮어씌워지는 오래된 표시값). EnableOAuthServiceProvider=true, GitLabSettings/OpenIdSettings는 둘 다 Enable=false(미설정). - VPS Mattermost에 OAuth 앱은 1건도 등록되어 있지 않음(oauthapps 테이블 0건). .env에 있는 MATTERMOST_OAUTH_CLIENT_ID/SECRET (xxxxxxxxxxxxxxx 등)는 로컬 전용이며, VPS에서는 미등록 = 무효한 값. - 【최중요】 Mattermost 컨테이너는 VPS 상에서 이미 weaver-redmine_redmine-network에도 접속 완료(Dual-homed). 즉, weaver-mattermost-mattermost-1 ⇔ redmine-prod:3000은 Docker 네트워크 내에서 직접 도달 가능. Cloudflare Tunnel을 경유하지 않는 서버 간 통신이 이미 성립된 상태. - 이메일 주소 불일치 재확인 (VPS):

  • Redmine: weaver_sales_mgr의 기본 이메일은 sales_mgr@weaver-demo.local (weaver_ 접두사 없음)

  • Mattermost: 동일 이름 사용자의 이메일은 weaver_sales_mgr@weaver-demo.local (weaver_ 접두사 있음, Redmine의 login과 일치)

  • → Redmine의 email이 아닌 login 값을 사용하면, MM 측의 기존 5개 계정과 이메일이 일치함.

  • Redmine:

  • Cloudflare Tunnel은 Cloudflare 대시보드 측에서 관리. 로컬에 ingress 정의 파일은 없으며, VPS 측에서는 라우팅 상세 내용을 확인할 수 없음 (대시보드 측은 사용자 확인이 필요함).

구현 접근 방식 (2가지 안)

안 A: GitLab SSO 방식으로 구현 (권장)

Redmine 플러그인에 GitLab API 호환의 경량 OAuth2 프로바이더 (/oauth/authorize, /oauth/token, /oauth/userinfo)를 구현하고, Mattermost의 GitLabSettings에서 "Redmine으로 로그인"을 활성화한다.

  • 장점: OAuth2 Authorization Code Flow + JSON REST API만으로 완결. JWT 서명/검증이 필요 없어 구현 및 디버깅이 용이함.
  • 단점: GitLabSettings는 MM 상에서 "레거시 (Legacy)" 취급되지만, 기능 자체는 현행 버전에서도 동작함.

안 B: OpenID Connect (OIDC) 방식

OIDC 프로바이더 (ID 토큰의 JWT 서명, JWKS 공개 등)를 구현하고, OpenIdSettings로 연계한다.

  • 장점: 보다 "표준적"인 구성.
  • 단점: JWT 서명·검증 로직, Discovery/JWKS 엔드포인트 구현이 필요하여 공수가 크게 증가함. 데모 규모에서는 오버스펙.

안 A를 권장합니다.

설계 체크리스트

  • .env에 추가하는 내용은 기존 변수를 파괴하지 않음 (추가만 수행, idempotent)
  • 신규 컨트롤러·라우트는 기존의 mattermost_redirect / mattermost_sso 기능과 충돌하지 않음
  • client_secret은 .env로 관리 (코드에 하드코딩하지 않음)
  • 액세스 토큰/인가 코드는 Rails.cache의 단기 TTL (코드 60초, 토큰 1시간 정도)
  • 5개의 데모 계정 중 우선 1명 (weaver_sales1 등)으로 동작 확인 후 나머지에 전개
  • MM GitLabSettings 변경 전에 현재 설정값을 백업 (config.json의 스냅샷)
  • 채팅 버튼의 리다이렉트 대상 변경은 기존의 channel/issue 액션에 영향을 주지 않음

롤백 절차

  • Redmine: 추가 파일 (신규 컨트롤러, 라우트 1줄, .env 추가분)을 삭제/주석 처리하고 docker restart
  • Mattermost: GitLabSettings를 Enable: false로 되돌림 (System Console 조작, 즉시 반영)
  • 사용자 인증 방식 변경: migrate-auth

를 역방향(gitlab→email)으로 실행하면 복구 가능 - 채팅 버튼:
mattermost_redirect_controller.rb

를 한 줄 되돌리기만 하면 됩니다.

이 방침(안 A)으로 진행해도 될지, ENV 변수명이나 GitLabSettings의 엔드포인트 구성에 대해 인식을 맞출 수 있다면, Phase 2(구현)로 들어갑니다.

이것이 Phase 1의 출력입니다. "설계안을 내고 기다려라"라는 규칙을 적어두는 것만으로, 조사·2안 비교·체크리스트·롤백(Rollback) 절차까지 자율적으로 갖추어 가져옵니다. 그리고 설계 체크리스트에는 idempotent라는 단어가 자연스럽게 포함되어 있습니다. — 방금 전의 멱등성(Idempotency) 이야기가 여기서 나온다는 구조입니다.

Phase 2는 이 출력에 "안 A로 진행해 주세요"라고 한마디 답하는 것만으로 시작됩니다.

HARD STOP (되돌릴 수 없는 조작)

## 되돌릴 수 없는 조작 — 절대 정지
- 파일의 삭제·이동·일괄 치환 전에 반드시 확인을 받을 것.
- 배포·외부 서비스로의 전송·공개는 명시적인 허가가 나올 때까지 실행하지 말 것.
...

"운영 브랜치(Production branch)로의 push는 운영 배포를 의미한다"는 문구는, GitHub 연동으로 CI가 돌아가는 프로젝트에서는 한 줄 적어두는 것만으로도 사고를 상당히 줄일 수 있습니다.

하지만 「부탁」에는 물리적인 강제력이 없다

앞서 언급한 규칙은 실제로 잘 작동합니다. 하지만 문장은 어디까지나 "문맥"입니다. Claude는 이를 「지켜야 할 규칙」이 아니라 「참고할 정보」로 처리합니다. 즉, 저의 CLAUDE.md는 노력 목표입니다. 인간과 마찬가지입니다.

그래서 다음 단계로 도입한 것이 .claude/settings.json.claude/hooks/입니다.

같은 규칙을 더 강력한 방법으로 이중화하는 것 — 취업 규칙(CLAUDE.md)에 더해, 금고 열쇠(settings.json)도 채워두는 것이라는 생각입니다.

.claude/settings.json ── HARD STOP을 JSON으로 번역하기

CLAUDE.md의 「부탁」을 JSON의 「강제」로 번역한 것이 이것입니다.

{
"permissions": {
"allow": [
...

deny 규칙이 CLAUDE.md의 어떤 규칙을 번역한 것인지 대응 관계를 정리하면 다음과 같습니다.

settings.json의 deny 규칙대응하는 CLAUDE.md의 규칙
git push * main* / git push origin main*「운영 브랜치로의 push는 운영 배포를 의미한다」
git push --force*「되돌릴 수 없는 조작은 절대 정지」
git reset --hard*「기존 데이터에 파괴적인 변경이 일어나지 않았는가 (Phase 3)」
git clean -f*「파일의 삭제 전에 반드시 확인을 받을 것」
rm -rf *「파일의 삭제 전에 반드시 확인을 받을 것」
docker compose down -v* / --volumes*「배포 명령은 명시적인 허가가 나올 때까지 실행하지 말 것」
docker volume rm* / prune*DB 데이터(영구 볼륨) 보호

docker compose down -v가 deny에 포함된 이유는, Weaver의 경우 MySQL 데이터가 Docker 볼륨에 올라가 있기 때문입니다. -v 플래그 하나로 데이터가 삭제됩니다. "좋은 의도로 환경을 깨끗하게 만들려 했을 뿐인데" 결과적으로 MySQL 데이터가 통째로 승천합니다. 악의는 제로, 피해는 100%라는, 이상적일 정도로 불합리한 사고입니다.

.claude/hooks/block-dangerous.sh ── 명령어의 내용까지 보고 멈추기

deny 규칙은 명령어 단위의 차단이지만, 훅(Hook)은 명령어의 문자열 내용까지 검사할 수 있습니다. 종료 코드(Exit code) 2를 반환하면 해당 조작이 취소됩니다.

설계에서 가장 고민했던 부분은 입력을 받는 방식입니다. Claude Code가 훅으로 도구 입력을 전달하는 방법은 버전에 따라 환경 변수(CLAUDE_TOOL_INPUT)인지 표준 입력(stdin)인지 달라질 가능성이 있습니다. 따라서 양쪽 모두에 대응하도록 했습니다.

#!/usr/bin/env bash
# Weaver — 위험 명령어 차단 훅 (PreToolUse / Bash)
set -uo pipefail
...

「DELETE FROM」와 「UPDATE SET」를 일괄 차단한 이유

WHERE 절의 유무를 정규 표현식(Regular Expression)으로 정확하게 판정하는 것은 생각보다 어렵습니다. 여러 줄 구성, 서브쿼리(Subquery), 줄바꿈의 변동성 등으로 인해 판정 로직이 쉽게 무너지기 때문입니다. 그래서 안전한 방향을 택해 일괄 차단하기로 했습니다. 정당한 SQL도 여기서 멈추겠지만, 그럴 경우에는 내용을 확인한 뒤 수동으로 실행하면 됩니다. 멈춰서 곤란한 상황보다, 멈추지 않아서 발생하는 사고가 훨씬 더 치명적입니다. 이는 Claude Code와 설계를 논의한 끝에 나온 결론입니다.

(옵션) MEMORY.md / ERRORS.md로 세션을 넘기기

Claude Code는 매 세션마다 완전히 백지 상태로 시작합니다. 어제 그렇게 함께 고민했는데 "처음 뵙겠습니다"라고 인사하는 격입니다. 매일 아침 기억을 완전히 리셋하고 출근하는 동료와 같습니다. 매우 성실하지만 조금은 허무하죠. 인수인계에 사용할 두 파일을 프로젝트 루트 디렉토리에 두고, CLAUDE.md 서두에 "세션 시작 시 반드시 읽을 것"이라고 지시합니다.

# MEMORY.md (인수인계 기록)
## 불변의 사실
- 운영 URL: https://weaver.met-architect.com
...
# ERRORS.md (실패 로그)
## [날짜] 시도한 내용 (실패)
- 작업 내용 / 실패 증상 / 원인 / 결론 (다음에는 이 절차를 시도하지 말 것)

요약

전편과 후편을 합치면, 안전하게 자율 구현을 맡기기 위한 계층은 다음과 같습니다.

역할파일효력
응답 규칙 · 작업 규약CLAUDE.md"부탁" (강력하게 작용하지만 강제성은 없음)
권한 (allow/deny/ask).claude/settings.json툴 단위의 물리적 차단
명령어 내용 검사.claude/hooks/block-dangerous.sh문자열 레벨에서의 즉시 취소
세션 인수인계MEMORY.md / ERRORS.md문맥의 지속

CLAUDE.md로 "이렇게 움직여 달라"고 전달하고, settings.json으로 "이것은 깨뜨릴 수 없다"고 담보합니다. 그리고 hooks를 통해 "명령어의 내용까지 확인하여 멈춥니다". 이 삼중 방어 체계가 갖춰지면, 대담하게 작업을 맡겨도 사고가 나기 어려운 개발 체제가 됩니다.

이 "규칙을 정해 AI를 움직인다"는 사고방식은 제가 개발하고 있는 업무 통합 기반 Weaver의 설계 방식 그 자체이기도 합니다. 지방의 시스템 아키텍트로서, 앞으로도 "규칙으로 AI를 길들이는" 지견을 계속해서 발신하겠습니다.

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0