
내가 작성한 설정 예시를 그대로 복사한 사람의 설정을 내 도구가 삭제해 버렸다
요약
Claude Code 사용자를 위한 설정 파일(settings.json) 관리 중 발생할 수 있는 심각한 오류 사례를 분석합니다. 주석이 포함된 JSON 복사로 인한 구문 오류가 진단 도구의 오작동과 기존 설정 삭제로 이어지는 과정을 다룹니다.
핵심 포인트
- JSON 파일 내 주석 포함 시 구문 오류로 인해 설정 로드 실패
- 에러를 묵인(swallow)하는 잘못된 예외 처리의 위험성
- 깨진 설정 파일을 빈 설정으로 오인하여 기존 데이터를 덮어쓰는 문제
- 설정 파일 검증 및 안전한 쓰기 로직의 중요성
Claude Code의 후크(hooks)를 909개 정도 만들어 배포하고 있다. 위험한 명령어를 차단하거나, 쓰기 전에 백업을 취하는 등, 그런 작은 셸 스크립트(shell script)들의 모음이다.
그 배포물을 점검하던 중, 가장 난처한 상황이 발견되었다. 순서대로 적으면 다음과 같다.
- 내가 작성한 설정 예시를 그대로 복사하면,
settings.json이 JSON으로서 깨진다. - 깨진 설정 파일에 작성된 후크는 단 하나도 작동하지 않게 된다. 화면에는 아무것도 나타나지 않는다. - 나의 진단 도구는 "후크가 하나도 없습니다"라고 말한다. 깨져 있다고는 말하지 않는다.
- 그 안내에 따라 후크를 추가하면, 원래의 설정이 전부 삭제된다. 게다가 성공했다고 표시된다.
세 가지 모두 별개의 결함이지만, 한 명의 이용자가 순차적으로 겪게 되는 구조로 연결되어 있었다.
이하는 2026년 8월 3일 시점의 Claude Code 2.1.220에서, 격리된 환경에 실제로 배치하여 확인한 내용이다.
기술 기사의 설정 예시에는 다음과 같은 방식이 자주 쓰인다.
// ~/.claude/settings.json
{
"hooks": { ... }
...
앞부분의 // 파일명은 "이것이 어떤 파일인가"를 나타내는 친절한 주석이다. 작성자의 의도는 코드 외부에 있는 설명으로 두려는 것이다.
하지만 독자는 블록 전체를 선택해서 복사한다. 그리고 settings.json은 엄격한 JSON이므로, //가 한 줄 있는 것만으로 파일 전체의 구문(syntax)이 깨진다.
깨진 설정 파일이 로드되면, 그곳에 작성된 후크가 단 하나도 작동하지 않게 된다. 한 글자 오타라면 하나만 죽겠지만, 이 경우는 통째로 전부다.
자신의 배포 페이지를 기계로 스캔했더니, 이런 형태가 4곳에 있었다. 전부 내가 작성한 것이었다.
게다가 한 곳은 JSON 중간에도 주석을 가지고 있었다.
"matcher": "Bash", // Only Bash commands
이것은 앞부분의 한 줄을 발견해서 지워도 여전히 남아 있다.
배포 페이지를 수정한 후, 겸사겸사 자신의 인스톨러(installer)를 세어 보았다. settings.json을 순수한 JSON.parse로 읽고 있는 곳이 34곳 있었고, 그중 9곳이 동일한 라인에서 실패를 완전히 묵인(swallow)하고 있었다.
try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch(e) {}
깨진 설정 파일은 에러조차 발생하지 않고 "빈 설정"으로 취급된다.
곤란한 것은 그 직후다. 이 함수는 환경의 건강 진단용이며, 읽어들인 설정을 보고 다음과 같이 판정한다.
if (preHooks.length === 0) {
risks.push({ severity: 'CRITICAL',
issue: 'No PreToolUse hooks — destructive commands can run unchecked' });
...
이용자는 후크를 설정해 두었다. 단지 읽지 못했을 뿐이다. 그것을 "하나도 설정되어 있지 않습니다"라고 보고하며 재설치를 권장한다. 원인(JSON이 깨져 있음)에 대해서는 한마디도 언급하지 않는다.
9곳을 하나씩 살펴보니, 그중 6곳은 읽은 후에 설정 파일을 다시 쓰고 있었다.
let settings = {};
if (existsSync(SETTINGS_PATH)) {
try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch {}
...
읽지 못했을 때, settings는 {}인 상태다. 거기에 새로운 후크를 하나 추가하여 원래 파일에 덮어쓴다.
격리된 환경에서 실제로 실행해 보았다. 깨진 settings.json에 기존 후크 1개와 permissions.allow 및 env를 두었다. 거기에 내 도구로 후크를 하나 추가한다.
| 종료 코드 | 원래 설정 | 기존 후크 | 환경 변수 | |
|---|---|---|---|---|
| 수정 전 | 0 (성공으로 표시) | 덮어씌워짐 | 사라짐 | 사라짐 |
| 수정 후 | 1 | 무사함 | 남음 | 남음 |
종료 코드는 0이며, 화면에는 "Registered in settings.json"이라고 나온다. 이용자는 성공했다고 생각할 것이다.
순서대로 정리하면 다음과 같다.
- 독자가 내가 작성한 설정 예시를 블록째로 복사한다
settings.json이 JSON으로서 깨진다 - 후크 (Hook)가 전부 멈춘다. 화면에는 아무것도 나오지 않는다- 독자가 나의 진단 도구를 실행한다 → 「후크가 하나도 없습니다」라고 나온다. 깨져 있다고는 말하지 않는다
- 독자가 안내받은 대로 후크를 추가한다 → 기존 설정이 전부 사라진다
내 책에는 「깨진 설정 파일을 기본값으로 되돌렸더니, 후크도 권한도 백업 없이 사라졌다」라는 사고를 한 장 분량으로 써두었다. 그 사고를, 내 도구가 일으키는 입장에 서게 되었다.
고쳤다. 깨져 있다면 쓰지 않고 멈춘 뒤, 원인과 확인 명령어를 출력한다. 덧붙여, 동일한 시험 도중에 --protect가 ReferenceError로 한 번도 성공하지 못했다는 사실도 알게 되어 그 부분도 고쳤다.
측정 방법은 3번 틀렸다. 3번 모두 「문제가 적은」 쪽으로 치우쳐 있었다.
첫 번째는, 수정 전 버전을 다른 곳에 두고 실행했기에 의존성 (Dependency)을 해결하지 못해 중단되었다. 출력이 비어 있는 것을 「결함이 없다」고 읽을 뻔했다.
두 번째는, 비교 기준을 「현재 최신」으로 설정했기에, 내가 커밋 (Commit)한 순간에 기준이 움직여 「수정 전도 고쳐져 있다」고 나왔다.
세 번째가 가장 위험했다. 「뭉개버린(suppress) 후 다시 쓰기(write back)를 할 것인가」를 셀 때, 각 지점의 뒤쪽 60행만 보고 있었다. 어떤 함수는 뭉개버린 지점과 다시 쓰는 지점이 117행 떨어져 있어서, 「읽기 전용」으로 분류해 버렸다. 함수의 범위로 다시 세어보니 5곳이 아니라 6곳이었다. 거리로 자른 창 (Window)은, 멀리 있는 결과를 놓친다.
후크가 올바르게 작동하여 위험한 조작이 한 번도 들어오지 않았을 때와, 후크가 단 하나도 호출되지 않았을 때, 화면에 나오는 것은 같다. 아무것도 나오지 않는다.
그러므로, 작동하고 있다는 것을 다른 방법으로 확인할 수밖에 없다. 한 줄이면 된다.
python3 -m json.tool ~/.claude/settings.json > /dev/null && echo "읽을 수 있음"
무언가 메시지가 출력된다면, 그 설정 파일에 작성한 후크는 전부 작동하지 않고 있는 것이다.
덧붙여, 등록 대상의 이름이 실제로 존재하는지도 확인해 두는 것이 좋다. 존재하지 않는 이름은 경고 없이 버려진다 (이 이야기는 7월 말에 다른 기사에서 썼으므로, 여기서는 확인 절차만 다룬다).
claude doctor
출력에 Unknown hook event가 나오면, 그 후크는 한 번도 호출되지 않은 것이다.
남에게 말하기 전에 나 자신을 세어 보았다. 배포 중인 후크 중, 등록 대상의 선언을 헤더 (Header)에 가진 것은 829개. 그중 2개가 존재하지 않는 이름을 선언하고 있었다. 둘 다 Any라고 적혀 있었다.
다른 후크를 감싸서 사용하는 도구이기에 「어떤 이벤트라도」라는 산문적인 의미로 의도한 것이었지만, 복사한 사람이 얻게 되는 것은 무시되는 키 (Key)다. 이를 수정하고, 다시는 이런 일이 발생하지 않도록 이름 검사를 CI (Continuous Integration)에 넣었다. 목록에 없는 이름이 있으면 빌드가 실패한다.
- 설정 예시를 블록째로 복사할 때, 맨 앞의
// 파일명을 가져가지 않는다. JSON에는 주석을 쓸 수 없다 - 기사나 README에 설정 예시를 쓰는 쪽은 주석을 블록 외부에 둔다. 독자는 반드시 블록째로 복사한다 - 설정 파일이 깨지면, 하나가 아니라 전부가 조용히 멈춘다. 확인은
python3 -m json.tool로 한 줄 - 설정을 읽고 다시 쓰는 도구는, 읽을 수 없을 때 절대로 쓰지 않는다. 읽을 수 없는 것을 「비어 있음」으로 간주하고 덮어쓰면, 이용자의 자산이 사라진다. 나는 34곳 중 9곳에서 실패를 뭉개버렸고, 그중 6곳이 다시 쓰기를 수행했다 - 셀 때, 창을 거리로 자르지 마라. 함수나 책임의 범위로 잘라라. 거리로 자른 창은 「문제가 적은」 쪽으로 치우친다
- 뭉개버린 결과로 나오는 것은 「설정되어 있지 않습니다」라는, 원인을 숨긴 진단이며, 그 안내를 따르면 설정이 사라진다
이 기사의 조사·검사·시정은 Claude Code를 사용하여 실행했다. 수치는 모두 로컬 환경 (WSL2 / Claude Code 2.1.220 / 909개의 후크)에서의 실측이며, 시정 내용은 MIT 라이선스로 공개하고 있다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기