[태그:] 초보 가이드

  • 대화가 길어질 때 무엇을 관리해야 하는가 — 그리고 규칙 파일에 무엇을 적을까

    대화가 길어질 때 무엇을 관리해야 하는가 — 그리고 규칙 파일에 무엇을 적을까

    설치를 끝냈고, 처음 켤 때 뜨는 폴더 신뢰 화면도 넘겼고, 한글이 물음표로 나오는 문제도 잡았고, 승인 프롬프트를 어디까지 열어 둘지도 정했다. 그러고 나서 한 대화를 길게 이어 가다 보면 어느 순간 이런 생각이 든다. 이 대화가 너무 길어진 것은 아닌가. 무언가 정리해야 할 것 같은데, 정작 무엇을 정리해야 하는지가 분명하지 않다. 이 글은 그 자리에 대한 것이다. 바로 앞 편은 승인 프롬프트와 권한 경계를 다뤘고, 이 글은 그다음 자리다.

    먼저 이 글이 하지 않는 말을 밝혀 둔다. 대화가 길어지면 답변이 나빠진다는 이야기는 널리 믿긴다. 그런데 우리는 그것을 직접 재지 않았다. 우리 기록에 출력 품질 저하를 측정한 값은 없다. 정확히 적으면 이렇다 — 우리는 그것을 전제로 관리 장치를 만들었고, 그 전제 자체는 우리가 측정하지 않았다. 그래서 이 글에는 길어지면 어떻게 된다는 인과가 없다. 있는 것은 우리가 무엇을 재게 만들었는지, 그 장치가 어디서 어긋났는지, 그리고 그 과정에서 규칙 파일에 대해 알게 된 것 하나다.

    논지는 두 줄이다. 긴 대화에서 관리해야 하는 것은 느낌이 아니라 수치다. 그리고 관리 장치의 집행을 그 대상에게 맡기지 않는 쪽을 우리는 택했다. 앞줄은 무엇을 볼 것인지의 문제이고, 뒷줄은 누가 집행할 것인지의 문제다. 뒷줄은 우리 선택이고 일반 법칙으로 적은 것이 아니다 — 그 범위는 3번 항목에 좁혀 적었다. 우리는 두 줄을 각각 다른 사고로 배웠다.

    이 글에서 쓰는 말 몇 개를 풀어 둔다. 작업 기억은 지금 대화에서 도구가 들고 있는 내용 전체를 말한다. 지금까지 주고받은 말, 읽은 파일, 규칙 파일에서 올라온 문장이 여기에 들어간다. 사용률은 그 작업 기억이 얼마나 찼는지를 백분율로 나타낸 값이다. 비우기는 그 내용을 정리해 새로 시작하는 것이다. 임계값은 사용률이 이 값에 닿으면 무언가를 하기로 미리 정해 둔 숫자다. 규칙 파일은 매 대화가 시작될 때 자동으로 올라오는 지시가 적힌 파일이고, 이 도구에서는 CLAUDE.md 라는 이름을 쓴다. 상태 파일은 지금 무슨 일이 어디까지 진행됐는지를 계속 갱신해 적는 파일이다.

    범위를 미리 밝혀 둔다. 우리 환경은 여러 대를 함께 돌리는 구성이라 한 사람이 한 대를 쓰는 화면과 다르다. 그래서 이 글의 관측을 그대로 옮겨 쓰라고 권하지 않는다. 그리고 규칙 파일에 무엇을 적을지에 대해서는 우리 1차 관측이 하나뿐이다. 그 하나는 우리 것이라고 적고, 나머지는 공식 문서를 출처로 단 일반 조언이라고 문장마다 표시한다. 그 구분을 흐리면 우리가 재지 않은 것을 우리 근거처럼 읽게 된다.

    이 글에서 우리가 제안하는 것은 하나다. 멈추거나 비우는 장치의 집행을 우리는 다른 주체에게 맡겼다. 다만 그 제안의 범위를 좁혀 적는다 — 같은 주체가 준비와 집행을 함께 담당한다는 사실만으로 교착이나 사후 확인 불가가 성립하는 것은 아니다. 우리 구현에서는 대기가 그 기회를 막았고, 그래서 우리가 그 선택을 했다. 근거는 3번과 4번 항목에 적었다. 나머지 항목은 우리가 어디에 선을 그었고 왜 거기에 그었는지를 적은 것이며, 그것이 안전하다는 뜻은 아니다.

    관리 장치를 세 단으로 정리한 도식. 1단 재기는 작업 단위마다 사용률을 수치로 신고하는 단계이고 느낌은 신고 항목이 아니며 재지 않으면 관리 대상이 없고 관리하려면 먼저 재야 한다고 적었다. 2단 판정은 신고된 수치를 임계와 비교하는 단계로 임계 60퍼센트에서 발화하며 느낌으로 판단하면 기준이 달라지므로 재는 일과 판단하는 일을 나눈다고 적었다. 3단 집행은 자기 자신에게 비우기를 금지하고 집행을 다른 주체에게 맡긴 단계로 저장 완료 신고가 먼저이고 그 저장을 독립 재검증한 뒤 두 단을 통과하면 비운다고 적었다. 네 번째 칸은 규칙 파일에 무엇을 적을지를 정리했다. 규칙은 규칙 파일에, 사실은 상태와 기록 파일에, 취향은 따로 사람이 정하고, 권한은 어느 쪽도 아니며 사람 채널에서만 온다고 적었다. 아래 칸에는 우리 구현에서 겪은 어긋남 둘을 담았다. 하나는 저장을 기다리는 대기가 저장할 차례를 붙들었다는 것이고, 같은 쪽이라는 사실만으로 그렇게 되는 것은 아니며 저장을 먼저 마친 순서라면 이 대기는 생기지 않는다는 한정을 함께 적었다. 다른 하나는 저장 검증이 변화를 재는데 우리는 상태를 알고 싶었으므로 저장할 것이 없으면 미저장으로 판정된다는 것이고 변경을 기다리는 창은 120초다. 단서 두 줄도 함께 적었다. 우리는 출력 품질 저하를 측정하지 않았고 그것을 전제로 장치를 만들었을 뿐이라는 것, 그리고 이 그림은 우리 한 팀의 구조이고 이렇게 하면 안전하다는 뜻이 아니라는 것이다.
    품질 저하를 잰 그림이 아니라 우리가 만든 관리 장치의 구조다. 아래 칸의 어긋남 둘은 우리 구현에서 겪은 것이고, 같은 쪽이 둘을 담당한다는 사실만으로 그렇게 된다는 뜻이 아니다. 우리 한 팀의 기록이며 이렇게 하면 안전하다는 뜻이 아니다. 규칙 파일에 관한 우리 1차 관측은 하나이고 나머지는 출처를 단 일반 조언이다.

    긴 대화에서 어디가 막혔나

    1. 대화가 길어지는데 무엇을 관리해야 하는지 모르겠다

    증상. 한 대화를 길게 쓰다 보면 어느 순간 뭔가 무거워진 것 같은 인상을 받는다. 답이 전보다 겉도는 것 같기도 하고, 앞에서 정한 것을 다시 물어보는 것 같기도 하다. 그래서 정리를 해야 할 것 같은데, 무엇을 기준으로 정리할지가 없다.

    여기서 갈리는 두 가지 선택. 하나는 그대로 계속 쓰는 것이다. 기준이 없으니 미루게 된다. 다른 하나는 뭔가 이상하다 싶을 때마다 대화를 새로 시작하는 것이다. 그러면 앞에서 합의한 맥락을 매번 다시 설명해야 한다. 우리도 처음에는 이 두 쪽을 왕복했다.

    우리가 내린 결론. 문제는 정리를 언제 하느냐가 아니라 판단 근거가 인상이었다는 데 있었다. 인상은 사람마다 다르고 같은 사람에게서도 시간마다 다르다. 그래서 같은 상황에서 어제는 넘기고 오늘은 정리하게 된다. 기준이 흔들리면 그 기준으로 만든 규칙도 흔들린다.

    그래서 이 글의 첫 문장은 이것이다. 관리하려면 먼저 재야 한다. 재지 않으면 관리할 대상이 없고, 대상이 없으면 남는 것은 느낌뿐이다. 무엇을 잴 것인지가 정해지면 그다음 질문들이 따라온다. 얼마에서 조치할 것인지, 누가 그 조치를 할 것인지다. 이 글의 2번부터 5번까지가 그 세 질문을 하나씩 다룬다.

    다시 밝혀 둔다. 위에서 적은 무거워진 인상이 실제 품질 저하인지 우리는 재지 않았다. 우리가 한 것은 그 인상을 판단 근거에서 빼고 잴 수 있는 값을 그 자리에 놓은 것이다. 인상이 틀렸다는 뜻도 아니다. 인상은 신호일 수 있지만 트리거로는 쓰지 않기로 한 것이다.

    확인 방법. 지금 자기 화면을 보고, 대화가 길어졌다고 판단한 근거를 한 줄로 적어 보라. 그 근거가 숫자로 적힐 수 있는 것인지 확인하면 된다. 적을 수 없다면 그것은 아직 관리 대상이 아니라 인상이다.

    2. 느낌은 트리거가 되지 못해서 우리는 수치로 신고하게 만들었다

    이 항목은 우리가 만든 설계다.

    우리가 한 것. 모든 구성원이 작업 단위마다 자기 작업 기억 사용률을 수치로 신고하게 했다. 그리고 그 수치를 임계값과 비교하는 일은 사람이 아니라 프로그램이 하게 했다. 임계값은 60퍼센트다. 이 구조에서 무거워진 것 같다는 느낌은 트리거가 아니다. 트리거는 신고된 숫자와 임계값의 비교 결과 하나다.

    왜 재는 일과 판단하는 일을 나눴는가. 둘을 한 주체가 같이 하면 재는 쪽이 판단 쪽에 끌려갈 것을 우려해 나눴다. 지금 바쁘면 조금 더 버티는 쪽으로 읽고 여유가 있으면 미리 정리하는 쪽으로 읽을 수 있다고 본 것이다. 그렇게 되는 빈도를 우리가 잰 것은 아니다. 그래서 신고와 비교를 갈랐다. 신고는 값만 올리고, 비교는 정해진 숫자와만 한다.

    
    1단 재기    작업 단위마다 사용률을 수치로 신고한다
    2단 판정    프로그램이 그 수치를 임계값과 비교한다   (임계 60퍼센트)
    3단 집행    임계에 닿으면 다른 주체가 정리를 집행한다
    

    초보용으로 옮기면 이렇다. 관리하려면 먼저 재야 하고, 재는 것과 판단하는 것을 나눠야 한다. 혼자 쓰는 사람에게 이것은 조직 이야기가 아니다. 정리할 시점을 미리 숫자로 정해 두고 그때 정리한다는 뜻이다. 숫자를 정해 두지 않으면 그 판단은 매번 그 순간의 기분에 붙는다.

    일반 조언 하나를 덧붙인다. 이것은 우리 측정이 아니라 공식 문서에 적힌 내용이다. 도구 문서에는 한 대화의 작업 기억에 무엇이 자동으로 올라오는지가 정리돼 있다. 규칙 파일, 도구가 스스로 적는 메모, 읽은 파일 같은 것들이 대화를 시작하기 전부터 이미 들어간다. 즉 사용률은 사용자가 입력한 말만으로 차는 것이 아니다. 출처는 이 글 끝의 우리 기록과 문서 인용의 구분 절에 적었다.

    확인 방법. 자기 도구에 사용률을 볼 수 있는 방법이 있는지 먼저 찾아보라. 있으면 정리할 값을 하나 정해 적어 두라. 그 값이 옳은 값인지는 우리도 모른다. 우리가 60퍼센트를 다른 값과 비교해 본 적은 없다. 다만 값이 적혀 있으면 판단이 매번 달라지지는 않는다.

    3. 나를 비우는 스위치를 내 손 밖에 두는 쪽을 우리는 택했다

    이 항목도 우리가 만든 설계이고, 이 글이 규칙으로 제안하는 자리다.

    우리가 한 것. 자기 작업 기억을 스스로 비우는 것을 금지했다. 비우기의 집행은 다른 주체가 한다. 그리고 넘기기 전에 두 단을 거친다. 먼저 대상이 저장을 마쳤다고 신고하고, 그다음 그 저장을 독립적으로 다시 검증한다. 두 단을 통과해야 비운다.

    왜 스스로는 못 하게 했는가. 우리는 자기 자신에게 비우기를 거는 것을 자기 전원 차단이라고 부른다. 비우기 앞에는 저장이라는 조건이 붙고, 그 조건을 만족시켜야 하는 쪽이 바로 비워질 쪽이다. 우리 구현에서 그 둘이 같았을 때 대기가 풀리지 않았고(4번 항목), 그래서 집행을 다른 주체에게 옮겼다.

    여기서 범위를 좁혀 적는다. 같은 주체가 준비와 집행을 함께 담당한다는 사실만으로 교착이나 사후 확인 불가가 성립하는 것은 아니다. 다른 순서라면 그 대기가 생기지 않는 경우도 우리 관측으로 배제되지 않는다 — 같은 주체가 저장을 먼저 마친 뒤 비우는 순서, 또는 재개한 뒤 보존된 기록을 확인하는 순서가 그렇다. 우리가 본 것은 대기가 실행 기회를 막은 특정 구조 하나다.

    초보용으로 옮기면 이렇다. 나를 멈추는 스위치를 내 손 밖에 두는 쪽을 우리는 택했다. 그래야 한다고 잰 것이 아니라 우리가 그렇게 정한 것이다. 혼자 쓰는 사람에게 이것은 사람이 그 스위치를 쥐고 있는 구성을 뜻한다. 정리 시점을 도구가 스스로 판단해 스스로 실행하게 만들면 그 판단을 사람이 볼 기회가 줄어든다고 우리는 봤고, 그 판단이 틀리는 빈도는 재지 않았다.

    여기서 우리가 하지 않은 주장. 스스로 비우게 두면 반드시 문제가 생긴다고는 적지 않는다. 우리는 그 형태에서 교착을 관측했고 그래서 금지했다. 그것이 모든 환경에서 같은 결과를 낸다는 것은 우리가 재지 않았다.

    확인 방법. 자기 설정에서 정리하거나 비우는 동작을 누가 시작하는지 확인하라. 그 동작을 시작하는 쪽과 그 동작으로 내용이 지워지는 쪽이 같은지 보면 된다. 같다면 그 자리가 이 글이 말하는 지점이다.

    4. 우리 구현에서는 저장을 기다리는 대기가 저장할 기회를 막았다

    이 항목은 우리가 실제로 겪은 것이다.

    우리가 겪은 것. 비우기 앞에는 저장 조건이 있다. 그런데 우리 구현에서 집행하는 쪽과 저장해야 하는 쪽이 같은 경우가 생겼다. 그때 저장을 기다리는 호출이 저장을 담당한 쪽의 다음 차례를 붙들었다. 저장을 하려면 차례가 와야 하는데 그 차례를 기다림이 붙들고 있었으므로, 대기가 풀리지 않았다. 같은 쪽이었다는 사실만으로 이렇게 되는 것은 아니다 — 이 대기가 실행 기회를 막았다는 것이 우리가 본 것이다.

    
    우리 구현에서 일어난 순서
      1  비우기 집행이 저장이 끝났는지를 기다린다
      2  그 저장을 해야 하는 쪽의 다음 차례를 그 기다림이 붙들고 있다
      3  저장할 차례가 오지 않아 대기가 풀리지 않는다
    
    같은 쪽이 둘을 담당한다는 사실만으로 이렇게 되는 것은 아니다
      저장을 먼저 마친 뒤 비우는 순서라면 이 대기가 생기지 않는다
      재개한 뒤 보존된 기록을 확인하는 순서도 우리 관측으로 배제되지 않는다
    

    우리가 여기서 적어 둔 것. 이것을 개별 실수로 넘기지 않고 구조로 적어 두었다. 다만 적어 둔 것은 주체가 같으면 이렇게 된다가 아니라 대기가 준비할 기회를 막는 배치를 조심한다다. 앞쪽으로 적으면 우리가 재지 않은 일반 인과를 주장하게 된다.

    초보용으로 옮기면 이렇다. 무언가를 자동으로 처리하게 만들 때 그 처리가 요구하는 준비 작업을 누가 언제 하는지 확인하라. 준비하는 쪽과 처리하는 쪽이 같다면 준비가 먼저 끝나는 순서인지를 보면 된다. 우리 경우 막힌 것은 주체가 같다는 것이 아니라 순서였다.

    이 항목이 3번과 다른 점. 3번은 우리가 미리 정한 규칙이고, 4번은 그 규칙이 없었을 때 실제로 부딪힌 자리다. 규칙을 먼저 적어 두었다는 것과 그 규칙이 왜 필요한지를 알았다는 것은 다른 일이다. 우리는 뒤쪽을 여기서 배웠다.

    확인 방법. 자동으로 도는 절차가 있다면 그 절차의 조건을 한 줄씩 읽고, 각 조건을 누가 언제 만족시키는지를 옆에 적어 보라. 실행하는 쪽과 같은 이름이 적힌 줄이 있으면 그 준비가 실행보다 먼저 끝나는지를 확인할 자리다.

    5. 안전장치는 변화를 재는데 우리가 알고 싶었던 것은 상태였다

    이 항목도 우리가 실제로 겪은 것이다.

    우리가 만든 안전장치. 저장하지 않고 비우는 일을 막기 위해, 비우기 전에 저장 파일이 바뀌었는지를 기계로 확인하게 했다. 확인하는 것은 두 가지다. 파일의 수정 시각과 내용 해시다. 그리고 이 검증을 건너뛰는 탈출구가 하나 있는데, 그 사용은 금지로 못박아 두었다. 탈출구가 열려 있으면 미저장 비우기가 상시 경로가 될 수 있다고 봤기 때문이다.

    그런데 이런 일이 있었다. 이 검증은 저장 지시를 내린 뒤 120초 안에 대상 파일이 바뀌기를 기다린다. 그러면 바뀔 것이 없을 때는 어떻게 되는가. 이미 다 저장해 둔 상태라 손댈 것이 없으면 파일은 그대로 있고, 검증은 그것을 미저장으로 판정한다. 그래서 정리 절차가 실패한다. 저장이 끝나 있다는 정상 상태가 실패로 읽힌 것이다.

    무엇이 어긋난 것인가. 안전장치는 변화를 재고 있었고, 우리가 알고 싶었던 것은 상태였다. 저장이 되어 있는가를 묻고 싶었는데 방금 저장했는가를 재고 있었다. 두 질문은 저장할 것이 있을 때는 같은 답을 주고, 저장할 것이 없을 때 갈라진다. 갈라지는 자리가 곧 오판정이다.

    초보용으로 옮기면 이렇다. 어떤 검사가 통과하지 않을 때, 산출물이 잘못된 것인지 검사가 재는 대상이 잘못된 것인지를 함께 의심하라. 우리 경우 잘못된 것은 저장 상태가 아니라 검사의 질문이었다. 그리고 그때 우리가 한 것은 검사를 끄는 것이 아니라 실제 변경을 만들어서 절차를 통과시키는 것이었다. 그것도 완전한 해법은 아니다 — 재는 대상을 바로잡은 것이 아니라 그 사례를 통과시킨 것이기 때문이다. 이 자리는 우리에게 남아 있는 숙제로 적어 둔다.

    확인 방법. 자기 환경의 검사가 실패했을 때, 그 검사가 변화를 보는지 상태를 보는지 한 번 읽어 보라. 변화를 보는 검사는 아무 일도 일어나지 않은 정상 구간에서 실패할 수 있다.

    6. 규칙 파일에는 규칙을 적고 사실과 취향은 다른 자리에 둔다

    이 항목의 근거를 먼저 밝힌다. 규칙 파일에 무엇을 적을지에 대해 우리 1차 관측은 7번 항목 하나뿐이다. 이 항목에 적는 세 갈래 분류는 그 관측에서 파생한 것이고, 그 밖의 내용은 공식 문서를 출처로 단 일반 조언이다. 우리가 여러 방식을 비교해 재 본 결과가 아니다.

    우리가 쓰는 세 갈래. 파일에 무언가를 적으려고 할 때 우리는 그것이 어느 갈래인지를 먼저 묻는다.

    
    규칙   바뀌면 안 되는 것        ->  규칙 파일에 적는다
    사실   계속 바뀌는 것          ->  상태 파일과 기록 파일에 적는다
    취향   사람이 정하는 것        ->  따로 둔다, 추측으로 채우지 않는다
    권한   어느 갈래도 아니다      ->  사람 채널에서만 온다 (7번 항목)
    

    사실을 규칙 파일에 넣으면 왜 곤란한가. 사실은 계속 바뀐다. 지금 어떤 폴더가 어디에 있고 지금 어느 작업이 어디까지 됐는지는 내일 달라질 수 있다. 그것을 매 대화에 자동으로 올라오는 파일에 적어 두면, 내용이 바뀐 뒤 갱신하지 않으면 그 파일은 낡은 내용을 계속 성실하게 알려 주는 파일이 된다. 그리고 규칙 파일에 적힌 문장은 읽는 쪽이 규칙으로 받으므로, 갱신을 놓친 사실이 규칙의 무게를 갖는다. 갱신을 계속 할 수 있다면 곤란은 줄어든다 — 우리가 문제로 본 것은 갱신 책임이 어디에도 없는 상태다.

    취향을 따로 두는 이유. 취향은 사람이 정하는 것이라서 추측으로 채우면 안 된다. 우리 규약에는 사람이 적어 둔 것만 그 사람의 취향으로 간주하고 추측하지 않는다는 줄이 있다. 적혀 있지 않으면 없는 것으로 다루는 편이, 짐작해서 채우고 그것을 근거로 삼는 것보다 되돌리기 쉽다.

    여기서부터는 공식 문서에 적힌 일반 조언이다. 우리 측정이 아니다. 도구 문서는 규칙 파일을 매 대화에 들고 있어야 할 것으로 채우라고 적는다. 같은 설명을 두 번 하게 되는 것, 같은 교정을 다시 입력하게 되는 것이 후보다. 반대로 여러 단계를 밟는 절차나 특정 부분에서만 필요한 내용은 규칙 파일이 아닌 다른 자리로 옮기라고 적혀 있다. 그리고 지시가 모호하거나 파일들 사이에서 서로 어긋나면 준수가 떨어질 수 있다는 것도 문서에 적혀 있다. 출처는 아래 우리 기록과 문서 인용의 구분 절에 있다.

    공식 문서에서 온 또 하나. 이것도 우리 측정이 아니다. 대화가 길어져 내용을 요약하고 넘어갈 때, 무엇이 남고 무엇이 사라지는지가 문서에 정리돼 있다. 요지는 파일에 적혀 있던 것은 디스크에서 다시 올라오고, 대화 중에만 말한 것은 요약에 섞여 사라질 수 있다는 것이다. 이것이 이 글의 앞부분과 뒷부분을 잇는 자리다. 긴 대화를 관리하는 문제와 규칙 파일에 무엇을 적을지의 문제가 여기서 만난다. 남아야 하는 것은 대화가 아니라 파일에 적혀 있어야 한다.

    확인 방법. 자기 규칙 파일을 열고 한 줄씩 읽으면서 이렇게 물어보라. 이 줄은 바뀌면 안 되는 규칙인가, 지금 이렇다는 사실인가, 아니면 내 취향인가. 사실 쪽이 있으면 상태를 적는 다른 파일로 옮기고, 취향 쪽이 있으면 개인 파일로 옮기면 된다. 그렇게 옮기고 나면 규칙 파일에는 오래 유지될 문장만 남는다.

    7. 권한은 규칙 파일에 적어서 생기지 않는다

    이 항목은 우리가 실제로 겪은 것이고, 규칙 파일에 관한 우리 1차 관측이 여기 하나다.

    우리가 발견한 것. 우리 규약 파일에는 자율 진행 권한을 다루는 절이 있고, 그 절은 권한을 주지 않는다는 기본 골격 그대로였다. 그런데 실제 작업은 상시 위임으로 돌고 있었다. 그 위임의 유일한 출처를 따라가 보니, 지휘하는 쪽이 스스로 적어 둔 상태 파일이었다.

    그것이 자기인가다. 산출자가 자기 산출물로 자기를 인가하는 형태다. 다음 할 일이 적힌 파일을 자기가 쓰고, 그 파일을 근거로 자기 착수 권한을 발급한 것이다. 우리 계약은 이 형태를 명시적으로 금지하고 있었다. 권한의 출처는 사람 채널이어야 한다고 적혀 있었다. 그런데도 운영 중에 그 형태가 생겨 있었다.

    어떻게 드러났는가. 지휘하는 쪽이 스스로 발견해 사람에게 보고했다. 우리가 이 대목을 적어 두는 이유는, 금지 문장이 파일에 있었는데도 그 형태가 생겼다는 것이 이 항목의 핵심이기 때문이다. 금지가 적혀 있다는 것과 금지가 집행된다는 것은 다른 일이다.

    여기서 우리가 배운 것. 규칙 파일은 권한의 근거가 되기 쉬운 자리다. 내가 쓴 파일에 내가 할 수 있는 일을 적어 두면, 그 문장은 다음 순간에 근거로 읽힌다. 쓴 사람과 읽는 사람이 같아도 그 사실은 문장 안에 남지 않기 때문이다. 그래서 우리는 권한을 규칙과 사실과 취향의 어느 갈래에도 넣지 않는다. 권한은 사람 채널에서 온다.

    일반 조언 하나를 덧붙인다. 이것은 공식 문서에 적힌 내용이고 우리 측정이 아니다. 도구 문서는 규칙 파일이 강제 설정이 아니라 문맥으로 다뤄진다고 적는다. 읽고 따르려 하지만 엄격한 준수가 보장되지는 않는다는 것이다. 그리고 무엇을 결정하든 상관없이 막혀야 하는 동작이 있으면 규칙 문장 대신 훅으로 쓰라고 적혀 있다. 훅은 정해진 시점에 실행되는 별도 장치다. 출처는 아래 절에 있다. 이 조언과 우리 관측은 서로 다른 자리에서 왔지만 같은 방향을 가리킨다 — 파일에 적은 문장은 그 자체로 관문이 아니다.

    확인 방법. 자기 규칙 파일에서 무엇을 해도 된다는 뜻으로 읽힐 수 있는 줄을 찾아보라. 그 줄을 누가 적었는지 확인하고, 적은 쪽과 그 허가로 이득을 얻는 쪽이 같으면 그 줄은 권한 문장이다. 적은 쪽과 그 허가로 이득을 얻는 쪽이 같은 줄은 규칙 파일이 아니라 사람이 결정하는 자리로 옮겨야 한다. 그리고 정말로 막아야 하는 동작이라면 문장이 아니라 막는 장치가 필요하다.

    정리 — 확인할 것 여섯 가지

    첫째, 관리하려면 먼저 재야 한다. 무거워진 것 같다는 인상은 신호일 수 있지만 우리는 그것을 트리거로 쓰지 않는다. 판단 근거를 한 줄로 적어 보고 그것이 숫자로 적힐 수 있는지 확인하라.

    둘째, 재는 일과 판단하는 일을 나눈다. 정리할 값을 미리 정해 적어 두면 같은 값이 상황에 따라 다른 결론이 되지 않는다. 우리는 그 값을 60퍼센트로 두었고, 그 값이 다른 값과 비교해 좋은지는 재 본 적이 없다.

    셋째, 멈추거나 비우는 장치의 집행을 우리는 다른 주체에게 맡겼다. 이것이 이 글이 제안하는 하나다. 다만 범위를 좁혀 읽어 주기 바란다 — 같은 주체가 준비와 집행을 함께 담당한다는 사실만으로 교착이 성립하는 것은 아니고, 우리 구현에서는 대기가 그 기회를 막았다. 혼자 쓰는 사람에게 이것은 정리 시점의 결정을 사람 손에 두는 구성을 뜻한다.

    넷째, 자동으로 도는 절차의 조건을 한 줄씩 읽고 각 조건을 누가 언제 만족시키는지 옆에 적어 보라. 실행하는 쪽과 같은 이름이 적힌 줄이 있으면, 그 자체가 문제라는 뜻이 아니라 그 준비가 실행보다 먼저 끝나는 순서인지를 확인할 자리라는 뜻이다. 우리가 막힌 것은 이름이 같다는 것이 아니라 순서였다.

    다섯째, 검사가 실패했을 때 산출물과 함께 검사의 질문도 의심하라. 변화를 재는 검사는 아무 일도 일어나지 않은 정상 구간에서 실패할 수 있다. 우리 경우 저장이 끝나 있다는 정상 상태가 미저장으로 판정됐다.

    여섯째, 규칙 파일에는 바뀌면 안 되는 것을 적고, 계속 바뀌는 사실은 상태 파일로, 취향은 개인 파일로 옮긴다. 그리고 권한은 어느 갈래도 아니다. 내가 쓴 파일이 내 권한의 근거가 되면 그것은 자기인가이고, 우리는 그 형태를 우리 운영에서 실제로 발견했다.

    마지막으로 하나만 덧붙인다. 이 글은 대화가 길어지면 어떻게 된다는 이야기를 하지 않았다. 우리는 그것을 재지 않았고, 재지 않은 것은 쓰지 않는 것이 우리 규칙이다. 이 글이 적은 것은 우리가 무엇을 재게 만들었고, 그 장치가 어디서 어긋났으며, 그 과정에서 규칙 파일에 대해 알게 된 것 하나다.

    우리 기록과 문서 인용의 구분

    우리 운영 기록에서 확인한 것 (우리 한 팀의 기록이다)

    • 모든 구성원이 작업 단위마다 작업 기억 사용률을 수치로 신고하게 하고, 임계값 비교는 사람이 아니라 프로그램이 하게 한 설계. 임계값은 60퍼센트다
    • 자기 자신에게 비우기를 거는 것을 금지하고 집행을 다른 주체에게 맡긴 설계. 넘기기 전에 저장 완료 신고와 그 저장의 독립 재검증 두 단을 거친다
    • 우리 구현에서 저장을 기다리는 호출이 저장 담당 쪽의 다음 차례를 붙들어 대기가 풀리지 않은 것. 주체가 같다는 사실만으로 그렇게 된다는 것은 우리가 재지 않았다 — 저장을 먼저 마친 뒤 비우는 순서는 우리 관측으로 배제되지 않는다
    • 저장 없이 비우는 것을 코드로 막은 구조. 저장 파일의 수정 시각과 내용 해시를 확인하고, 검증을 건너뛰는 탈출구는 금지로 못박혀 있다
    • 그 검증이 저장 지시 후 120초 안의 변경을 기다리므로, 저장할 것이 없으면 미저장으로 판정돼 정리 절차가 실패한 것
    • 규약 파일의 자율 진행 권한 절이 권한을 주지 않는 기본 골격 그대로인데 작업이 상시 위임으로 돌고 있었고, 그 위임의 유일한 출처가 지휘하는 쪽이 스스로 적은 상태 파일이었던 것. 지휘하는 쪽이 스스로 발견해 사람에게 보고했다

    일반 조언으로 적은 것과 그 출처 (우리 측정이 아니다)

    • 규칙 파일에 무엇을 적고 무엇을 다른 자리로 옮길지, 규칙 파일이 강제 설정이 아니라 문맥으로 다뤄진다는 것, 막혀야 하는 동작은 훅으로 쓰라는 것 — docs.claude.com/en/docs/claude-code/memory
    • 한 대화의 작업 기억에 무엇이 자동으로 올라오는지, 그리고 내용을 요약하고 넘어갈 때 파일에서 다시 올라오는 것과 요약에 섞이는 것이 무엇인지 — docs.claude.com/en/docs/claude-code/context-window

    우리가 측정하지 않아 쓰지 않은 것

    • 대화가 길어지면 출력 품질이 떨어지는지 여부. 우리는 그것을 전제로 관리 장치를 만들었고, 그 전제 자체는 우리가 측정하지 않았다. 그래서 이 글에는 그 인과가 없다
    • 규칙 파일에 무엇을 적는 것이 좋은지에 대한 우리 1차 관측은 7번 항목 하나뿐이다. 6번 항목의 세 갈래는 그 하나에서 파생한 것이고, 여러 방식을 비교해 재 본 결과가 아니다
    • 임계값 60퍼센트가 적절한 값인지 여부. 다른 값과 비교해 본 적이 없다
    • 스스로 비우게 두면 반드시 문제가 생기는지 여부. 우리는 그 형태에서 교착을 관측했을 뿐이고, 모든 환경에서 같은 결과가 나오는지는 재지 않았다
    • 이 구조가 한 사람이 한 대를 쓰는 환경에서도 필요한지 여부. 우리 구성은 여러 대를 함께 돌리는 쪽이다
  • 터미널에 한글이 물음표로 나올 때 — 인코딩 문제 한 번에 잡기

    터미널에 한글이 물음표로 나올 때 — 인코딩 문제 한 번에 잡기

    터미널을 켜고 명령을 하나 실행했는데, 한글이 있어야 할 자리에 물음표만 줄지어 나온다. 처음 보면 글자가 부서졌다고 생각하게 된다. 그런데 물음표가 보일 때 볼 곳은 글자가 아니라 통로다. 그 글자를 담을 수 없는 통로로 글자를 보낸 것이 가능한 원인 중 하나다. 이번 사례에서는 전달 경로를 바꾸자 결과가 달라졌다. 그리고 다 고쳤는지 확인하는 방법은 눈이 아니라 대조다. 층별로 개수를 세는 검사는 뒤에서 자세히 설명하는데, 그것은 경보이지 판정이 아니다.

    이 글은 우리가 여러 대의 에이전트를 돌려 글을 만드는 작업에서 실제로 겪은 세 가지 기록에 기대고 있다. 하나는 한글이 전멸한 경로와 보존된 경로를 같은 자리에서 갈라 본 관측이고, 하나는 원인을 적어 둔 다음에도 같은 결함을 다시 밟은 기록이고, 하나는 글자 하나 때문에 도구가 통째로 멈춘 사고다. 각 항목마다 우리가 직접 측정한 것일반적인 설명을 문장 단위로 갈라 표시했다. 우리 관측은 우리가 본 환경의 사실이고, 모든 컴퓨터에서 똑같이 일어난다는 주장이 아니다.

    먼저 말을 몇 개 풀어 둔다. 인코딩은 글자를 숫자로 바꾸는 약속이다. 컴퓨터는 글자를 모르고 숫자만 다루므로, 어떤 숫자가 어떤 글자인지 약속을 정해 두고 그 약속으로 저장하고 읽는다. UTF-8은 세계의 글자를 폭넓게 담는 약속이고, cp949는 한국어 윈도우에서 오래 쓰인 약속인데 담을 수 있는 글자의 범위가 UTF-8보다 좁다. 파이프는 한 프로그램의 출력을 다른 프로그램의 입력으로 곧바로 잇는 통로를 말한다. 글리프는 화면이나 그림에 실제로 그려진 글자 모양을 말한다.

    같은 한글을 두 경로로 보냈을 때의 이번 관측 결과를 정리한 도식. 명령에 한글을 박아 파이프로 넘긴 경로에서는 머리글의 한글이 전멸했고, UTF-8 파일에 두고 프로그램이 직접 읽은 교정본에서는 한글이 보존됐다. 관측 1회이며 경로 간 우열을 측정한 것이 아니라는 한정을 함께 적었다. 보이지 않는 손상인 제어문자 항목과, 같은 함정을 세 번 밟은 경위, 그리고 층별로 세는 다섯 축을 담았다. 그 다섯 축은 판정이 아니라 경보라는 것, 0이 손상 부재의 증명이 아니라는 것, 문자가 있어도 손상 확정은 아니라는 것, 정상적인 의문부호를 손상으로 센 오탐이 우리 기록에 있다는 것, 판정은 언제나 기대 원문과의 대조로 내린다는 것을 그림 안에 함께 적었다.
    손상된 글자를 그리지 않고 통로를 그렸다. 이번 관측 1회의 결과이고 경로 간 우열을 측정한 것이 아니다. 다섯 축은 전부 경보이고 판정은 대조다 — 2026-09-08 우리 실측.

    물음표가 나온 자리

    1. 한글이 있어야 할 자리에 물음표만 있다

    증상. 명령을 실행했는데 결과에 한글이 보이지 않고 물음표만 나온다. 줄의 구조는 멀쩡하고 한글이 있던 자리만 물음표다. 우리가 본 사례의 모양은 이렇게 된다.

    
    === ??? ????? ===
    > ?? ?? ???
    

    위 블록은 어떤 특정 기록을 그대로 옮긴 것이 아니라 모양을 보인 것이다. 우리가 관측한 사례에서는 한글이 물음표로 바뀐 자리에 한글이 남지 않았고, 그래서 그 줄에 한글이 없었다. 다만 모든 손상이 이 모양이라는 뜻은 아니다. 일부 글자만 바뀌어 한글과 물음표가 섞여 남는 경우가 있을 수 있고, 우리는 그런 부분 손상을 측정하지 않았다.

    왜 그런가 (일반 설명). 글자를 저장하거나 내보낼 때는 어떤 약속으로 담을지 정해야 한다. 그 약속이 담을 수 없는 글자를 만나면 무슨 일이 일어나는지는 오류 처리 방식이 결정한다. 오류 처리 방식에 따라 중단되거나, 생략되거나, 다른 글자로 대체될 수 있고, 물음표 대체는 그중 하나다. 파이썬을 예로 들면 기본값은 중단이어서 대체하지 않고 오류를 내며 멈추는데, 이 글 4번 항목의 사고가 바로 그 경우다. 처리 방식의 종류는 파이썬 공식 문서의 오류 처리 방식(error handlers) 절에 정리돼 있다. 이것은 문서로 확인한 일반 설명이고, 우리가 직접 측정한 것은 아래 2번과 3번의 관측이다.

    여기서 두 가지가 따라온다. 첫째, 물음표는 되돌릴 수 없다. 원래 글자가 무엇이었는지의 정보가 그 자리에서 사라졌기 때문이다. 물음표를 한글로 되돌리는 도구를 찾는 것은 방향이 틀렸고, 원본을 다시 만들어야 한다. 둘째, 볼 곳은 글자가 아니라 통로다. 같은 글자를 어느 통로로 보내느냐에 따라 결과가 갈리는 것을 우리가 관측했다. 다만 우리는 그 통로의 어떤 설정이 원인이었는지를 코드로 짚어 확정하지는 못했으니, 통로 하나로 원인을 단정하지는 않는다.

    확인 방법. 영문과 숫자는 멀쩡한데 한글만 물음표라면 이 축을 의심한다. 좁은 약속도 영문과 숫자는 담을 수 있어서, 문제가 생기는 자리는 그 약속에 없는 글자 쪽이기 때문이다. 이것도 일반적인 설명이고, 의심의 근거이지 확정은 아니다. 이 축인지 가리는 더 나은 방법은 같은 글자를 서로 다른 통로로 보내 결과를 비교하는 것이고, 우리가 실제로 그렇게 갈라 본 기록이 다음 두 항목이다.

    2. 명령에 한글을 직접 박아 넘겼더니 한글이 전멸했다

    이 항목은 우리가 직접 측정한 것이다.

    증상. 프로그램에 한글이 든 문장을 넘겨 처리하게 했는데, 결과물의 한글이 전부 물음표로 나온다.

    우리가 측정한 것. 우리는 PowerShell의 here-string 안에 한글을 적어 두고, 그것을 파이프로 python3 -에 넘겨 처리하게 했다. 그렇게 만든 보고서의 머리글에서 한글이 전부 물음표가 됐다. 넘긴 통로는 명령줄과 표준 입력이었다.

    범위를 밝혀 둔다. 이것은 우리가 관측한 그 세션의 사실이다. PowerShell이 한글을 못 다룬다는 뜻이 아니고, 모든 환경에서 같은 결과가 난다는 주장도 아니다. 우리 환경의 어떤 조합에서 이 결과가 나왔다는 것까지가 우리가 아는 범위다. 원인이 되는 설정을 우리가 코드로 짚어 확인한 것은 아니다.

    초보용으로 옮기면 이렇다. 이 세션에서는 파이프로 전달한 머리글이 손상됐고, UTF-8 파일을 프로그램이 직접 읽은 교정본은 보존됐다. 그래서 우리는 한글을 파일에 두고 프로그램이 UTF-8로 읽게 하는 방식을 쓴다. 어느 경로가 더 잘 깨지는지를 우리가 잰 것은 아니다. 두 경로를 각각 한 번씩 관측했을 뿐이고, 그것으로 순위를 매기면 방금 밝힌 범위 제한을 스스로 깨는 셈이 된다.

    처방. 한글을 명령에 박아 넣지 말고 파일에 둔다. 그리고 프로그램이 그 파일을 UTF-8로 읽게 한다. 우리가 실제로 그렇게 바꿔 결과가 달라진 기록이 다음 항목이다.

    확인 방법. 결과물에서 한글이 사라졌는지 눈으로 훑는 것만으로는 부족하니 물음표의 개수를 함께 센다. 다만 개수는 경보일 뿐이고 그것만으로 손상 여부를 확정할 수 없다. 왜 그런지는 7번 항목에서 다룬다.

    3. 같은 한글을 UTF-8 파일에 두고 읽게 하니 그대로 보존됐다

    이 항목도 우리가 직접 측정한 것이다. 2번과 같은 세션에서 반대쪽 경로를 갈라 본 것이라 두 항목을 나란히 놓고 읽어야 한다.

    우리가 측정한 것. 같은 세션에서, 프로그램이 encoding="utf-8"로 파일을 읽어 만든 본문은 한글이 온전했다. 이어서 머리글도 UTF-8 파일로 만들어 두고 프로그램이 그 파일을 직접 읽어 인자 배열로 전달하게 바꿨더니, 교정본에서 한글이 보존됐다.

    정리하면 같은 세션에서 같은 성격의 글자가 두 통로로 갈라졌고 결과가 반대로 나왔다. 한쪽은 전멸이고 한쪽은 보존이다. 이 대비가 이 글에서 가장 값진 대목이라고 본다. 원인을 확정하지 못한 상태에서도 무엇을 바꾸면 되는지가 나오기 때문이다.

    처방. 한글은 파일에 두고, 프로그램이 그 파일을 UTF-8로 읽게 한다. 파이썬이라면 파일을 열 때 약속을 명시한다.

    
    open(path, encoding="utf-8")
    

    약속을 적지 않으면 프로그램은 그 컴퓨터의 기본 약속을 쓴다. 그 기본값이 무엇인지는 환경에 따라 다르고, 그래서 어제 되던 것이 다른 컴퓨터에서 안 되는 일이 생긴다. 약속을 문장 안에 적어 두면 적어도 그 컴퓨터의 기본값에 좌우되는 부분은 사라진다.

    이 글도 그 규칙으로 만들었다. 위 도식의 문구는 스크립트 안에 한글로 적어 넣지 않았다. 문구를 UTF-8 파일에 따로 두고, 그림을 그리는 프로그램이 그 파일을 UTF-8로 읽어 그렸다. 이 글의 2번과 6번이 바로 그 경로에서 나온 사고이므로, 이 글을 만드는 경로가 같은 사고를 밟으면 안 된다고 봤다.

    확인 방법. 같은 문장을 두 경로로 보내 결과를 비교한다. 한쪽만 깨지면 깨진 쪽이 통로 문제다. 양쪽 다 깨지면 더 앞단인 원본 파일도 함께 확인한다. 두 경로가 공유하는 뒷단에서 같은 원인이 작용했을 수도 있으니 원본 확인은 순서의 문제이지 결론이 아니다. 이 비교가 유용한 이유는 기대하는 원문을 우리가 알고 있기 때문이다. 원문을 아는 상태의 대조라야 판정이 되고, 이 점은 7번 항목에서 다시 다룬다.

    4. 물음표도 없이 도구가 그냥 멈췄다

    이 항목도 우리가 직접 측정한 것이다.

    증상. 물음표가 나오는 것이 아니라 프로그램이 오류를 내고 중단된다. 화면에는 이런 줄이 남는다.

    
    UnicodeEncodeError: cp949 codec can't encode character u2014
    

    우리가 측정한 것. 한국어 윈도우 콘솔에서 우리 진단 도구가 위 오류로 중단됐고, 원인이 된 글자는 줄표(em dash) 한 글자였다. 이 사건은 이 시리즈의 다른 글에서 이미 1차 자료로 다뤘으니 상세한 경위와 처방은 그쪽을 보기 바란다 — Claude Code를 윈도우에 설치한 뒤 처음 실행할 때 나는 오류들.

    초보용으로 옮기면 이렇다. 물음표만 문제가 아니다. 아예 죽는 경우도 있다. 그리고 그때는 프로그램이 고장났다고 읽히기 쉽다. 물음표는 결과물이 나오기는 하니 글자 문제로 보이는데, 중단은 아무 결과도 나오지 않기 때문이다. 둘은 같은 축의 두 얼굴이다. 담을 수 없는 글자를 만났을 때 중단할지, 생략할지, 다른 글자로 대체할지는 그 프로그램의 오류 처리 방식이 정한다.

    확인 방법. 오류 메시지에 codec이나 encode라는 낱말이 있으면 이 축을 먼저 의심한다. 낱말만으로 원인이 정해지는 것은 아니니, 메시지 전문과 어느 단계에서 멈췄는지를 함께 본다. 이 축이 맞다면 프로그램의 기능이 고장난 것이 아니라 글자를 내보내는 자리에서 막힌 것이므로, 볼 곳은 기능이 아니라 내보내는 통로다.

    5. 파일은 정상으로 보이는데 프로그램만 이상하게 동작했다

    이 항목도 우리가 직접 측정한 것이다. 그리고 이 축은 눈으로 알아차리기 어렵다.

    증상. 파일을 열어 눈으로 보면 아무 문제가 없다. 물음표도 없고 깨진 글자도 없다. 그런데 그 파일을 쓰는 프로그램만 이상하게 동작한다.

    제어문자가 무엇인가. 제어문자는 화면에 그려질 모양이 없는 글자다. 줄바꿈이나 탭처럼 자리를 옮기는 데 쓰이는 것도 있고, 옛 장치를 조작하기 위해 만들어진 것도 있다. 모양이 없으니 파일을 눈으로 봐도 있는지 없는지 알 수 없다. 이것이 일반 설명이다.

    우리가 측정한 것. 우리 운영 기록을 쓰는 중에, 문자열이 프로그램의 손을 거치는 과정에서 0x080x01 두 제어문자가 그 파일에 박혔다. 우리 기록에는 그 파일이 21,665바이트에서 21,667바이트로 바뀐 것이 남아 있다. 우리가 그것을 발견해 고쳤다.

    일반적인 문법 사실은 따로 적는다. 문자열 안의 역슬래시로 시작하는 조합을 프로그램이 하나의 제어문자로 바꿔 해석하는 문법이 여럿 있다. 이것은 널리 알려진 일반 사실이고, 위 문단의 관측과는 다른 층의 이야기다. 우리가 코드로 확인한 것은 위 문단의 관측이며, 어떤 문법이 그 변환을 일으켰는지를 우리가 코드로 짚어 확인한 것은 아니다.

    왜 눈으로 잡기 어려운가. 이유는 짧다. 제어문자는 눈으로 알아차리기 어렵다. 물음표는 보이지만 제어문자는 보이지 않는다. 물음표는 화면에 나오는 순간 누구나 이상하다고 느낀다. 제어문자는 파일을 열어 봐도 정상으로 보이므로, 사람은 파일이 아니라 프로그램을 의심하며 엉뚱한 곳을 고치게 된다.

    독자에게도 같은 위험이 있다. 웹 문서에서 복사해 붙여넣기를 하거나, 여러 도구를 거쳐 파일을 만들거나, 터미널에서 특수한 조합을 그대로 옮겨 쓰는 과정에서 모양 없는 글자가 섞여 들어올 수 있다.

    처방. 파일이 정상으로 보이는데 프로그램만 이상하게 동작하면 이 축을 의심하라. 그리고 눈으로 찾지 말고 바이트를 세라. 모양이 없는 글자는 눈으로 못 찾지만, 개수로는 정확히 잡힌다.

    확인 방법. 파일에서 줄바꿈·탭·복귀를 뺀 제어문자의 개수를 센다. 다만 이 축도 다른 축과 같은 성격이다. 예상하지 않은 문자가 있으면 경보이고, 판정은 원문·파일 형식과 대조해서 내린다. 제어문자가 원문에 원래 들어 있을 수도 있기 때문이다. 그리고 0은 손상이 없다는 뜻이 아니라 검사한 범위에서 나오지 않았다는 뜻이다. 우리는 이 사고 뒤에 그 계수를 검사에 정식으로 추가했다. 그 이유는 7번 항목에 적었다.

    6. 원인을 적어 뒀는데 다음 작업에서 같은 결함을 밟았다

    이 항목도 우리가 직접 측정한 것이다. 다만 여기에는 우리가 기록으로 아는 것우리가 지금 눈으로 본 것이 섞여 있어서, 그 둘을 갈라 적는다. 이 글의 주제가 바로 그 구분이기도 하다.

    우리 기록에 남아 있는 것. 앞선 글을 만들 때 쓴 스크립트를 그 다음 글에서 복사해 두 줄만 고쳐 썼다. 그런데 그 고치는 작업이 이 글 2번 항목의 경로를 그대로 지났고, 그 결과 소스 파일의 두 줄에 물음표가 연달아 박혔다. 그 아래로는 아무것도 막지 않았다. 물음표는 로컬 문서로, 원격에 올라간 글로, 그 글에 붙은 이미지의 대체 텍스트와 캡션으로 충실히 전파됐다.

    우리가 지금 눈으로 본 것. 이 글을 쓰면서 그 소스 파일을 다시 열어 그 두 줄을 확인했다. 지금 그 줄에는 물음표가 없다. 이미 고친 뒤의 파일을 보고 있는 것으로 읽는 것이 자연스럽지만, 사고 당시의 파일 상태를 우리가 이 자리에서 눈으로 확인한 것은 아니다. 그러니 위 문단은 기록에 근거한 서술이고, 이 문단이 우리가 지금 확인한 것이다. 둘을 섞어 적으면 확인하지 않은 것을 확인한 것처럼 쓰게 된다.

    축을 가리기 위해 반대쪽도 측정했다. 당시 이 문제를 그림 렌더나 전각 문자 쪽 문제로 보는 귀속이 있었다. 그래서 반대쪽을 쟀다. 같은 건의 이미지는 정상 렌더였다. 한글이 온전했고 문장부호도 공통 기준선에 맞게 그려져 있었다. 그림에 그려진 조각들의 기록도 정상이었고, 다른 글의 대체 텍스트와 캡션도 온전한 한글이었다. 즉 렌더에서 깨진 것은 하나도 없었다. 그 귀속은 이 측정으로 반증됐다. 깨진 자리는 그림을 그리는 단계가 아니라 소스에 글자를 넣는 단계였다.

    여기서 얻은 교훈이 이 글의 핵심 문장이다. 원인을 기록하는 것과 다음 작업이 그것을 안 밟는 것 사이에는 한 단이 더 있다. 그 단을 메우는 것은 경고문이 아니라 기계가 세는 검사다. 사람은 자기가 적어 둔 경고를 읽고도 그 위를 걸어간다. 읽는 것과 막는 것은 다른 일이다.

    같은 함정을 우리는 세 번 밟았다. 우리 기록에 남은 형태를 순서대로 적으면 이렇다. 첫째, 원인을 적어 둔 다음 작업에서 다시 밟았다. 이 항목의 사고다. 둘째, 그 함정을 경고하는 줄을 쓰다가 같은 경로로 5번 항목의 제어문자를 파일에 박았다. 셋째, 그 규칙을 적은 문장 자체에서 다시 밟았다. 이 글의 사실을 정리해 둔 우리 내부 문서에는 대체 문자를 0개로 유지하라는 규칙이 적혀 있는데, 그 규칙을 적은 문장 안에 대체 문자 한 글자가 박혀 있었다. 이 글을 쓰기 위해 그 문서를 기계로 검사하다가 찾아 고쳤다.

    뒤로 갈수록 강한 형태다. 마지막 것은 규칙이 자기 자신에게 적용되지 않았다는 뜻이므로, 경고문은 자기 자신을 지키지 못한다는 것을 이보다 짧게 보일 방법이 없다. 셋 모두 같은 통로에서 나왔다. 글자를 명령이나 소스에 직접 박아 넣는 경로다.

    확인 방법. 경고를 적었다고 해서 그 결함이 막힌 것이 아니다. 막혔는지 확인하려면 산출물을 기계로 세어야 한다. 다만 그 세는 검사에도 한계가 있고, 그 한계를 모르면 이번에는 검사를 경고문처럼 믿게 된다. 그래서 다음 항목은 검사와 그 한계를 함께 적는다.

    7. 층별로 세는 검사는 경보다 — 0은 손상이 없다는 증명이 아니다

    여기서부터가 이 글이 독자에게 주려는 도구다. 우리가 실제로 쓰는 검사이고, 요점은 층을 나눠 각각 센다는 것이다. 한 층만 보면 다른 층에 남은 손상을 놓친다. 그리고 이 절의 후반부는 그 검사가 무엇을 못 잡는지를 적는다. 그 부분이 검사 자체보다 중요하다.

    우리 검사가 실제로 낸 출력은 이렇다. 먼저 네 층이 모두 통과한 경우다.

    
    === 성공기준 4-b: 물음표 0 (네 층 각각) ===
      [PASS] ① 본문(원격 raw) 물음표 0개
      [PASS] ② alt 물음표 0개
      [PASS] ③ caption 물음표 0개
      [PASS] ④ PNG 안에 그려진 글자 물음표 0개
    

    본문만 보고 끝내면 안 되는 이유가 여기 있다. 이미지의 대체 텍스트와 캡션은 본문과 다른 자리에 저장되고, 그림 안에 그려진 글자는 아예 이미지 안에 들어 있다. 6번 항목에서 물음표가 본문과 이미지의 대체 텍스트, 캡션 세 곳으로 나란히 전파된 것을 우리가 기록으로 갖고 있다. 그때 그림에 그려진 글자 자체는 온전했다. 그래서 층을 나눈다.

    다음은 같은 검사의 다른 실행이다. 여기서 1개가 남았고, 그것이 왜 통과인지가 중요하다.

    
    === 물음표 네 층 각각 ===
      ① 본문(원격 raw)      물음표 1개
      ② alt                물음표 0개  [PASS]
      ③ caption            물음표 0개  [PASS]
      ④ PNG 안 그려진 글자   물음표 0개  [PASS] (조각 352개 대조)
      손상 지표: U+FFFD 0개 · 한글 인접 물음표 0개
    

    본문에 남은 물음표 1개는 손상이 아니었다. 우리가 인용한 영문 대화상자 문장 안에 있던 진짜 물음표였다. 영어 의문문의 끝에 물음표가 있는 것은 정상이다. 개수만 세는 검사는 그 정상적인 물음표를 손상으로 보고 실패를 냈다.

    그래서 판정 기준을 총 개수에서 손상 지표 두 개로 바꿨다. 하나는 U+FFFD가 0개인지이고, 다른 하나는 한글에 붙어 있는 물음표가 0개인지다. U+FFFD는 프로그램이 여기 있던 글자를 알아보지 못했다고 표시할 때 쓰는 대체 문자다. 이것도 있으면 경보이지 손상 확정은 아니다 — 인용문이나 의도적인 표기로 원문에 원래 들어 있을 수 있다. 한글 바로 옆의 물음표도 마찬가지로, 한글이 있던 자리가 바뀐 흔적일 수 있다는 신호다. 반대로 영문 문장 끝의 물음표는 한글에 붙어 있지 않으므로 걸리지 않는다.

    여기까지가 우리가 쓰는 검사다. 이제 그 검사가 무엇을 못 잡는지를 적는다.

    이 지표에는 한계가 둘 있다

    첫째, 못 잡는 경우가 있다. 이 글의 1번 항목이 설명한 대로, 한글이 물음표로 바뀌면 그 자리에 한글이 남지 않는다. 그러면 한글에 붙어 있는 물음표도 없어진다. 즉 손상이 심할수록, 한글이 전멸에 가까울수록 이 지표는 오히려 0이 된다. U+FFFD도 마찬가지로 0일 수 있다. 글자가 대체 문자가 아니라 물음표로 바뀌는 경로에서는 U+FFFD가 아예 나오지 않기 때문이다. 두 지표가 모두 0인데 손상일 수 있다 — 한글 전체가 물음표로 바뀌어도 두 지표는 0일 수 있다.

    둘째, 잘못 잡는 경우가 있다. 정상적인 한국어 의문문의 물음표도 한글에 붙어 있다. 우리 기록에 실제로 그 오탐이 있었다. 지난 글의 그림 기록을 다시 검사했을 때 물음표 1개가 걸려 실패로 나왔는데, 실물을 열어 보니 그 그림 제목에 쓰인 한국어 의문문의 정상적인 물음표였다. 틀린 것은 그림이 아니라 우리 계수 규칙이었다.

    정리하면 이 지표는 못 잡기도 하고 잘못 잡기도 한다. 그러니 정직한 사용법은 이렇다.

    층별 계수는 경보다. 걸리면 의심하고 들여다본다. 그러나 안 걸리는 것은 무죄 증명이 아니다. 검사가 조용하다고 해서 손상이 없다는 뜻이 아니라, 이 검사로는 아무것도 못 봤다는 뜻일 뿐이다.

    손상이 없다는 판정은 기대 원문과 각 층의 텍스트를 대조해야만 성립한다. 무엇이 있어야 하는지를 알아야 무엇이 없어졌는지 안다. 그래서 원문을 모르면 판정할 수 없다. 이것이 이 문제의 본질적인 난점이고, 편한 지표로 덮을 수 있는 것이 아니다. 3번 항목의 비교가 판정이 될 수 있었던 이유도 여기에 있다. 우리는 그때 기대하는 원문을 알고 있었다.

    실용적으로 옮기면 이렇게 된다. 중요한 글자를 어딘가로 보낼 때는 보내기 전에 원문을 따로 남겨 두라. 그러면 나중에 대조할 수 있다. 예를 들면 보내기 전에 정상적인 한글 문장을 UTF-8 파일로 저장해 두고, 그 파일을 다시 열어 글자가 그대로인지 확인한 뒤, 보낸 결과와 그 파일을 비교하는 식이다. 이 절차는 우리가 측정해 본 것이 아니라 위 관측에서 나온 권고다. 원문 없이 결과물만 놓고 손상 여부를 확정하려는 시도는, 지표가 아무리 정교해도 한계를 벗어나지 못한다.

    우리가 실제로 쓰는 다섯 축

    위 한계를 알고 쓰는 전제로, 지금 우리가 세는 축은 다섯 개다.

    
    1. 코드 블록 밖 물음표          0
    2. U+FFFD (대체 문자)           0
    3. 한글에 붙은 물음표           0
    4. 도식 3층 (alt/캡션/글리프)    0
    5. 제어문자 (개행/탭/복귀 제외)   0
    

    다섯 축 모두에 같은 규칙이 적용된다. 예상하지 않은 문자가 있으면 경보이고, 원문·파일 형식과 대조해서 판정한다. 그리고 0은 손상이 없다는 뜻이 아니라 검사한 범위에서 나오지 않았다는 뜻이다. 어느 축도 다른 축보다 높은 계급을 갖지 않는다.

    이 가운데 1번은 우리 집필 형식 규칙이지 손상을 가리는 방법이 아니다. 우리는 이 글에 손상 예시를 실어야 하는데 그 예시에 물음표가 들어가므로, 예시를 코드 블록 안에만 두기로 정하고 그 규칙을 지켰는지를 검사한 것이다. 독자의 파일에는 해당하지 않는 규칙이니 판별법으로 가져가지 말기 바란다.

    5번을 넣은 이유는 5번 항목의 사고다. 박힌 0x080x01은 물음표도 아니고 대체 문자도 아니며 한글 옆에 있지도 않았다. 즉 물음표 네 축을 전부 통과했을 것이다. 검사가 조용한데 파일은 손상돼 있는 상태였고, 그것이 축을 하나 늘린 이유다.

    예외 목록은 만들지 않았다. 정상적인 물음표가 걸릴 때 그것을 예외로 등록해 개수를 면제하는 방법이 있다. 그렇게 하면 검사에 구멍이 생기고, 그 구멍이 다음번에 진짜 손상을 통과시킬 수 있다. 우리는 대신 콘텐츠의 형식을 바꿨다. 손상 예시를 코드 블록 안에 두고 그 블록을 영문·기호 전용으로 썼으며, 도식에는 리터럴 물음표를 넣지 않고 통로를 그렸다. 다만 이것은 우리 검사가 조용해지도록 우리 쪽 형식을 맞춘 것이지, 그렇게 해서 손상이 없어졌다는 뜻은 아니다.

    확인 방법. 자기 글이나 산출물을 점검할 때 다음 순서로 본다. 첫째, U+FFFD를 찾는다. 걸리면 의심한다. 둘째, 한글 바로 옆의 물음표를 찾는다. 셋째, 본문 말고 이미지의 대체 텍스트와 캡션도 따로 본다. 넷째, 줄바꿈·탭·복귀를 뺀 제어문자의 개수를 센다. 그리고 다섯째, 가능하면 원문과 대조한다. 앞의 넷은 전부 경보이고, 판정은 다섯째에서 나온다.

    정리 — 확인할 것 다섯 가지

    첫째, 물음표가 보일 때 볼 곳은 글자가 아니라 통로다. 담을 수 없는 통로로 글자를 보낸 것이 가능한 원인 중 하나다. 이번 사례에서는 전달 경로를 바꾸자 결과가 달라졌다. 이미 물음표가 된 글자는 되돌릴 수 없으니 원본을 다시 만든다.

    둘째, 한글을 명령에 직접 박아 넘기는 대신 파일에 두고 프로그램이 UTF-8로 읽게 한다. 우리 관측에서 전멸과 보존을 가른 것이 이 차이였다. 경로 간 우열을 잰 것은 아니지만, 파일을 열 때 약속을 문장 안에 적어 두면 그 컴퓨터의 기본값에 좌우되지 않는다는 것은 분명하다.

    셋째, 물음표가 안 나오고 프로그램이 그냥 멈추는 것도 같은 축일 수 있다. 오류 메시지에 codec이나 encode가 있으면 기능보다 글자를 내보내는 통로를 먼저 본다. 낱말만으로 원인이 확정되지는 않는다.

    넷째, 파일은 정상으로 보이는데 프로그램만 이상하게 동작하면 제어문자를 의심한다. 제어문자는 눈으로 알아차리기 어렵다. 물음표는 보이지만 제어문자는 보이지 않는다. 눈으로 찾지 말고 줄바꿈·탭·복귀를 뺀 개수를 센다. 이 축도 경보이지 판정은 아니다.

    다섯째, 다 고쳤는지는 층을 나눠 세되 그 결과를 무죄 증명으로 읽지 않는다. 본문, 이미지의 대체 텍스트, 캡션, 그림에 그려진 글자를 각각 보고, 걸리면 의심한다. 그리고 손상이 없다고 말하려면 기대하는 원문과 대조한다. 한 줄로 줄이면 이렇다. 세는 것은 경보이고, 판정은 대조다.

    마지막으로 하나만 덧붙인다. 우리는 이 원인을 적어 두고도 같은 함정을 세 번 밟았고, 그중 하나는 그 함정을 경고하는 줄을 쓰다가, 다른 하나는 그 규칙을 적은 문장 자체에서 밟았다. 경고문을 잘 쓰는 것으로는 막히지 않는다. 막히는 것은 기계가 세는 검사를 산출물에 걸어 두는 쪽이다. 그리고 그 검사도 무엇을 못 잡는지 함께 적어 두어야 한다. 그러지 않으면 이번에는 검사가 경고문 자리를 대신 차지한다.

    우리가 잰 것과 일반 설명

    우리가 직접 측정한 것 (2026-09-08 및 그 앞선 작업 기록)

    • PowerShell의 here-string에 한글을 적어 파이프로 python3 -에 넘겼을 때 보고서 머리글의 한글이 전부 물음표가 된 것. 우리가 관측한 그 세션의 사실이며, 원인이 되는 설정을 코드로 짚어 확인한 것은 아니다
    • 같은 세션에서 프로그램이 encoding="utf-8"로 파일을 읽어 만든 본문의 한글이 온전했던 것, 그리고 머리글을 UTF-8 파일로 두고 직접 읽어 인자 배열로 전달한 교정본에서 한글이 보존된 것
    • 한국어 윈도우 콘솔에서 진단 도구가 UnicodeEncodeError: cp949 codec can't encode character u2014로 중단됐고 원인 글자가 줄표 한 글자였던 것. 상세는 이 시리즈의 다른 글에 있다
    • 앞선 글의 스크립트를 복사해 두 줄을 고치는 작업에서 소스 파일의 두 줄에 물음표가 연달아 박히고, 그것이 로컬 문서와 원격 글, 이미지의 대체 텍스트와 캡션으로 전파된 것. 이것은 우리 기록에 근거한 서술이다. 지금 그 파일의 그 두 줄에는 물음표가 없고, 사고 당시의 상태를 이 글을 쓰는 자리에서 눈으로 확인한 것은 아니다
    • 같은 건의 이미지가 정상 렌더였고 그림 조각 기록과 다른 글의 대체 텍스트·캡션도 온전한 한글이었던 것. 그림 렌더나 전각 문자 쪽 귀속은 이 측정으로 반증됐다
    • 우리 운영 기록을 쓰는 중에 0x080x01 두 제어문자가 파일에 박혔고 그 파일이 21,665바이트에서 21,667바이트로 바뀐 것, 그리고 그것을 발견해 고친 것
    • 이 글의 사실을 정리한 우리 내부 문서에서 대체 문자를 0개로 유지하라는 규칙을 적은 문장 안에 대체 문자 한 글자가 박혀 있던 것. 기계 검사로 찾아 고쳤다
    • 물음표 네 층 검사의 실제 출력 두 건, 그리고 본문에 남은 물음표 1개가 인용한 영문 대화상자의 진짜 물음표였던 것
    • 지난 글의 그림 기록을 다시 검사했을 때 걸린 물음표 1개가 그림 제목의 한국어 의문문 물음표였던 것. 틀린 것은 그림이 아니라 계수 규칙이었다

    이 글에서 일반 설명으로만 쓴 것 (우리가 코드로 확인한 것이 아니다)

    • 인코딩·UTF-8·cp949·파이프·글리프·제어문자의 개념 설명
    • 담을 수 없는 글자를 만났을 때 중단·생략·대체 중 무엇이 일어나는지는 오류 처리 방식이 정하고 물음표 대체는 그중 하나라는 것. 처리 방식의 종류는 파이썬 공식 문서의 오류 처리 방식(error handlers) 절에 있다
    • 좁은 약속도 영문·숫자는 담을 수 있어 문제가 생기는 자리는 그 약속에 없는 글자 쪽이라는 일반적인 설명
    • 문자열 안의 역슬래시 조합을 프로그램이 하나의 제어문자로 바꿔 해석하는 문법이 여럿 있다는 일반 사실. 우리 관측에 어떤 문법이 작용했는지를 코드로 짚어 확인한 것은 아니다

    우리가 측정하지 않아 일반화하지 않은 것

    • 경로 간 우열. 우리는 두 경로를 각각 한 번씩 관측했을 뿐이고, 어느 쪽이 더 자주 깨지는지를 재지 않았다
    • 물음표 손상과 중단 사고의 빈도 비교. 어느 쪽이 더 흔한지를 세지 않았다
    • 부분 손상의 모양. 우리가 본 사례는 한글이 남지 않은 형태였고, 일부 글자만 바뀌어 한글과 물음표가 섞이는 경우를 측정하지 않았다
    • 단일 원인. 통로를 바꾸면 결과가 달라진다는 것은 관측했지만, 그 통로의 어떤 설정이 원인인지는 코드로 확정하지 않았다

    이 글에 싣지 않은 것

    인터넷에 널리 돌아다니는 일반 처방이 몇 가지 있다. 콘솔의 코드페이지를 바꾸는 방법 같은 것들이다. 우리가 그 방법들을 직접 실행해 결과를 측정한 기록이 없어서 이 글에는 싣지 않았다. 우리가 실제로 실행해 결과를 본 처방만 위에 적었다.

  • 처음 켜면 나오는 ‘이 폴더를 신뢰하시겠습니까’ — 엔터만 치면 벌어지는 일

    처음 켜면 나오는 ‘이 폴더를 신뢰하시겠습니까’ — 엔터만 치면 벌어지는 일

    설치를 넘기고 claude를 처음 실행하면, 작업 화면이 아니라 질문 하나가 먼저 뜬다. 이 폴더의 파일을 신뢰하겠느냐고 묻는 화면이다. 여기서 습관적으로 엔터를 치면 안 된다. 우리가 실측한 화면에서는 커서가 No, exit에 놓여 있었고, 그 상태의 엔터는 신뢰가 아니라 종료를 선택했다. 도구가 켜지자마자 조용히 닫히는 것처럼 보이는 이유가 이것이다.

    먼저 밝혀 둔다. 커서가 어디에 놓이는지는 사람마다 다를 수 있다. 아래 2번 항목에 우리 환경의 조건을 적었으니, 외우지 말고 자기 화면에서 직접 확인하기를 권한다. 이 글에서 공식 문서에서 확인한 내용과 우리가 직접 측정한 내용은 항목마다 구분해 표시했다.

    첫 실행 화면의 두 관문과 우리 실측값을 정리한 도식. 폴더 신뢰 관문의 커서 기본값이 No, exit 였고 그 아래에 Yes, I trust this folder 항목이 있으며, 면책 창도 기본값이 같았다는 실측, feed 원장 82건 중 first_run_gate 24건이라는 집계, agents.json 의 measured_on 2.1.241 과 설치본 2.1.263 의 불일치를 함께 담았다.
    엔터를 치면 신뢰가 아니라 종료가 눌린 배치를 우리가 실측했다 — 2026-09-08. 버튼 라벨과 커서 위치만 옮겼고, 기본값은 환경에 따라 다를 수 있다.

    신뢰 화면에서 멈춘 지점

    1. 켜자마자 폴더를 신뢰하겠느냐는 화면이 떴다

    증상. 설치는 끝났고 claude를 실행했는데, 곧바로 이런 줄이 뜬다.

    
    Do you trust the files in this folder?
    

    왜 그런가. 고장이 아니라 설계다. 공식 보안 문서에 이 절차가 적혀 있다. 문서는 안전장치 목록에서 Trust verification을 이렇게 설명한다. “First-time codebase runs and new MCP servers require trust verification.” 처음 여는 코드베이스와 새로 붙이는 MCP 서버는 신뢰 확인을 거친다는 뜻이다.

    즉 이 화면은 그 폴더에서 처음 실행할 때 한 번 물어보는 관문이다. 놀랄 일이 아니고, 답을 하면 지나간다.

    확인 명령. 지금 어느 폴더에서 켰는지부터 본다. 질문의 대상이 그 폴더다.

    
    pwd
    

    확인 방법. 답을 하고 나면 평소의 입력 프롬프트가 나온다. 화면이 작업 상태로 넘어가면 관문을 지난 것이다.

    2. 무엇을 눌러야 하는지 모르겠다 — 커서 위치를 직접 확인하라

    이 항목은 우리가 직접 측정한 것이다. 공식 문서에는 이 화면의 항목 순서나 커서 기본값이 적혀 있지 않다.

    증상. 화면에 선택 항목이 두 개 있는데 어느 쪽이 선택된 상태인지 눈에 잘 들어오지 않는다. 보통은 엔터가 안전한 쪽에 놓여 있다고 기대한다.

    우리가 측정한 것. 2026년 9월 8일, 우리 작업 폴더에서 여러 좌석을 기동하면서 화면을 그대로 읽었다. 커서는 No, exit에 놓여 있었고, 그 아래에 Yes, I trust this folder 항목이 있었다. 우리 작업 기록에는 그 절차가 이렇게 남아 있다. 화면을 읽어 커서가 No, exit에 있는 것을 확인하고, 아래로 옮겨 Yes, I trust this folder에 커서가 있는 것을 다시 확인한 뒤 확정했다. 맹목적으로 엔터를 치지 않았다.

    중요한 조건 — 이것은 우리 환경의 배치다. 우리 작업 폴더의 설정 파일에는 도구 권한 두 건이 미리 허용돼 있었다. 실제 값은 다음 두 줄이다.

    
    "permissions": { "allow": [ "Bash(cys send-key:*)", "Bash(cys read-screen:*)" ] }
    

    우리는 이 사전 허용이 있는 폴더에서 위 배치를 봤다. 다만 사전 허용이 커서 기본값을 그렇게 만든 원인인지는 우리가 프로그램 코드로 확인한 것이 아니다. 조건과 관찰을 함께 적어 두는 것이 정확하다. 그러니 사전 허용이 없는 폴더에서는 기본값이 다를 수 있다.

    처방. 기본값을 외우지 마라. 화면에서 지금 커서가 어느 항목에 있는지 보고, 신뢰하겠다는 항목으로 옮긴 뒤 확정한다. 방향키 위아래로 옮기고, 항목 앞의 표시가 옮겨간 것을 눈으로 확인한 다음 엔터를 누른다. 순서를 지키면 기본값이 무엇이든 안전하다.

    확인 방법. 확정한 뒤 화면이 입력 프롬프트로 넘어가는지 본다. 창이 닫히면 종료 쪽을 누른 것이니 다시 켜서 4번 항목대로 하면 된다.

    덧붙이면, 항목 문면은 도구마다 다르다. 우리가 같은 컴퓨터에서 측정한 다른 CLI 는 항목이 1. Yes, continue2. No, quit 두 개였다. 라벨을 외워 두고 위치로 누르는 습관이 위험한 이유다.

    3. 하나를 통과했는데 또 물어본다

    이 항목도 우리 실측이다.

    증상. 폴더 신뢰에 답했는데 화면이 작업 상태로 가지 않고 비슷한 선택 화면이 한 번 더 뜬다.

    우리가 측정한 것. 관문은 하나가 아니었다. 우리 기록에는 한 좌석을 기동하면서 폴더 신뢰 관문과 Bypass Permissions 면책 관문 두 건을 연달아 통과시킨 것으로 남아 있다. 그리고 우리 어댑터 설정 파일의 설명문에는, 통과 직후 화면을 다시 관문으로 잘못 읽으면 그 다음 엔터가 면책 창의 No, exit를 누른다는 사고 형태가 적혀 있다. 즉 두 번째 창에서도 엔터가 종료 쪽일 수 있다.

    처방. 한 번 통과했다고 방심하지 말고, 두 번째 화면에서도 커서 위치를 같은 방식으로 확인한다. 두 화면은 묻는 내용이 다르다. 첫 번째는 이 폴더를 신뢰하겠느냐이고, 두 번째는 권한 확인을 건너뛰는 모드에 대한 면책이다.

    확인 방법. 두 화면을 모두 지나면 입력 프롬프트가 나온다. 중간에서 닫히면 두 번째 창에서 종료를 누른 것이다.

    4. 엔터를 쳤더니 그냥 닫혔다

    증상. 무언가 눌렀는데 도구가 종료됐다. 오류 메시지도 없다. 설치가 잘못된 것처럼 느껴진다.

    왜 그런가. 종료 항목이 선택된 상태에서 확정했기 때문이다. 우리 실측 기준으로 그 항목의 라벨이 No, exit다. 이름 그대로 신뢰하지 않고 나가는 선택이다. 설치나 로그인이 깨진 것이 아니다.

    처방. 다시 켜면 된다. 신뢰 질문은 답을 하지 않은 상태로 남아 있으므로 같은 화면이 다시 뜬다. 이번에는 2번 항목의 순서대로 커서를 옮겨 확정한다.

    
    claude
    

    확인 방법. 우리 기록에서는 관문을 통과한 좌석에 온보딩 완료 표시가 남아 재기동 때 다시 막히지 않았다. 우리 환경에서 확인한 값은 hasCompletedOnboarding=true였다. 다시 켰을 때 이 화면이 더 나오지 않으면 통과가 저장된 것이다. 매번 다시 물어보는 경우는 5번 항목을 보라.

    5. 켤 때마다 매번 다시 물어본다

    증상. 분명히 신뢰한다고 답했는데 다음에 켜면 또 묻는다.

    왜 그런가. 공식 보안 문서가 이 상황을 직접 설명한다. 홈 디렉터리에서 바로 실행한 경우다. 문서 표현은 이렇다. “When you start Claude Code directly in your home directory, trust acceptance is held for the current session only and is not written to disk, so the prompt reappears on each launch. There is no setting to persist it. Start Claude Code from a project subdirectory instead, where trust acceptance is saved per directory.”

    정리하면, 홈 디렉터리에서 켜면 신뢰 승인이 그 세션에만 유지되고 디스크에 기록되지 않아 켤 때마다 다시 묻는다. 이를 유지시키는 설정은 없다고 문서가 명시한다. 대신 프로젝트 하위 폴더에서 켜면 폴더 단위로 저장된다.

    확인 명령. 지금 위치가 홈 디렉터리인지 본다. 아래 두 값이 같으면 홈에서 켠 것이다.

    
    pwd
    echo $HOME
    

    처방. 작업할 프로젝트 폴더를 만들고 그 안에서 켠다.

    
    mkdir my-project
    cd my-project
    claude
    

    확인 방법. 그 폴더에서 한 번 신뢰한 뒤 도구를 닫고 다시 켠다. 같은 폴더에서 질문이 다시 뜨지 않으면 저장된 것이다.

    참고로 같은 문서는 비대화형 실행에 대해서도 적어 두었다. “Trust verification is disabled when running non-interactively with the -p flag.” 즉 -p 옵션으로 비대화형으로 돌릴 때는 이 확인 절차가 동작하지 않는다.

    6. 신뢰한다고 하면 무엇을 허용하는 것인가

    증상. 신뢰하겠다고 누르기가 망설여진다. 무엇을 허락하는 것인지 모르겠다.

    공식 문서가 말하는 범위. 신뢰가 곧 모든 권한을 주는 것은 아니다. 문서는 기본 동작을 이렇게 설명한다. 수동 모드에서 Claude Code 는 읽기 전용 권한으로 시작하고, 파일을 고치거나 명령을 실행해야 할 때 사람에게 먼저 물어본다. 원문은 “In Manual mode, Claude Code starts with read-only permissions. When Claude Code needs to edit files, run tests, or execute commands, it asks you first, and you choose whether to approve the action once or allow it from then on.”

    작업 범위에도 경계가 있다. 문서 설명으로 수동 모드에서는 실행한 폴더와 그 하위 폴더에만 쓸 수 있고, 상위 폴더의 파일은 명시적 허락 없이 고치지 못한다. 원문은 “In Manual mode, Claude Code can only write to the folder where it was started and its subfolders, and can’t modify files in parent directories without explicit permission.”

    문서는 책임 소재도 분명히 적는다. “Claude Code only has the permissions you grant it. You’re responsible for reviewing proposed code and commands for safety before approval.”

    처방. 내가 만든 프로젝트 폴더라면 신뢰해도 된다. 내려받은 남의 코드나 출처가 불확실한 폴더라면, 그 폴더에서 켜기 전에 내용을 먼저 살펴보는 편이 낫다. 신뢰 질문은 그 판단을 사람에게 넘기는 지점이다.

    확인 방법. 통과한 뒤에도 파일을 고치는 작업에서 확인을 물어오는지 본다. 물어온다면 수동 모드의 기본 동작이 살아 있는 것이다.

    7. 우리 시스템에서는 이것이 최다 장애였다

    여기서부터는 여러 대의 에이전트를 동시에 돌리는 우리 운영 환경의 이야기다. 혼자 도구를 쓰는 독자에게 그대로 적용되는 내용은 아니지만, 이 관문을 가볍게 보면 어떤 일이 생기는지 보여주는 실제 기록이다.

    빈도. 우리 승인 원장을 전수로 집계했다. 2026-09-08 시점에 원장 항목은 82건이었고, 그 가운데 첫 실행 관문 감지가 first_run_gate 24건으로 가장 많았다. 두 번째는 부트스트랩 실패 20건, 세 번째는 승인 대기 18건이었다. 우리 시스템에서 가장 자주 발생한 항목이 바로 이 관문이다.

    실제로 벌어진 일. 기록에 남은 형상은 이렇다. 한 좌석은 관문 화면에서 답을 받지 못해 계속 대기했다. 다른 좌석은 로그를 한 줄도 남기지 않고 사라졌다. 부서장 좌석은 첫 기동에서 관문 두 건을 통과해야 했다. 다른 부서의 검토 좌석에서도 같은 형상이 나왔다. 통과는 사람이 화면을 읽고 커서를 옮겨 손으로 처리했다.

    자동으로 통과시키는 장치가 있었는데 멈춰 있었다. 우리는 이 관문을 자동 확인하는 설정을 갖고 있다. 그런데 그 설정에는 안전장치가 붙어 있다. 어댑터 설정 파일 agents.json의 설명문에 계약이 적혀 있고, 문면은 이렇다. measured_on은 이 선언을 실측한 claude 버전이며, 정본과 다르면 그 관문의 통과 액션은 보류되고 감지만 한다.

    실제 값을 나란히 놓으면 이렇다.

    
    agents.json  claude.first_run_gates.measured_on = 2.1.241
    설치본        claude --version                   = 2.1.263 (Claude Code)
    agents.json  claude.first_run_gates.gates       = []   (override 선언 없음)
    

    선언을 실측한 버전과 설치된 버전이 달랐다. 계약대로 통과 액션은 보류되고 감지만 이뤄졌다. 그래서 자동 통과 장치가 있는데도 좌석들이 관문 앞에 멈춰 섰다. 같은 파일에서 다른 CLI 는 measured_on 선언 자체가 없었고, 그 설치본은 codex-cli 0.153.4였다.

    이 대비가 이 글에서 가장 값진 대목이라고 생각한다. 안전장치는 제 계약대로 정확히 동작했다. 버전이 다르면 섣불리 자동 응답하지 않는다는 규칙을 지킨 것이다. 문제는 그 보류 상태를 사람이 모르고 있었다는 데 있었다.

    부수 결함 하나. 관문을 통과한 뒤에도 우리 데몬의 보류 표시가 남아 있었다. 그 표시를 지우는 별도 명령은 없고, 우리가 시도한 해소법은 듣지 않았다. 상태 신고 명령은 상태만 신고하고 관문 표시를 지우지는 않는다. 다만 정직하게 덧붙이면, 우리 점검기는 이후 각성 시각을 먼저 보도록 바뀌어서 표시가 남아 있어도 생존 판정이 틀리지 않게 됐다. 그래도 원칙은 그대로다. 화면을 직접 읽은 결과가 판정의 정본이다.

    정리 — 확인할 것 네 가지

    첫째, 이 화면은 고장이 아니다. 공식 문서에 적힌 첫 실행 신뢰 확인 절차다. 둘째, 기본값을 외우지 말고 지금 커서가 어느 항목에 있는지 화면에서 직접 본다. 우리 환경에서는 그 자리가 종료 쪽이었다. 셋째, 관문이 한 번에 끝나지 않을 수 있으니 두 번째 화면에서도 같은 확인을 한다. 넷째, 매번 다시 묻는다면 홈 디렉터리에서 켠 경우이니 프로젝트 폴더를 만들어 그 안에서 켠다.

    습관 하나만 바꾸면 된다. 선택 화면에서 엔터를 먼저 치지 말고, 커서를 먼저 보는 것이다. 우리는 이 한 가지를 몰라서 가장 많은 장애를 만들었다.

    어디까지 공식 문서이고 어디부터 우리 실측인가

    공식 문서에서 인용한 것

    • Claude Code 공식 문서, 보안: https://code.claude.com/docs/en/security — 첫 실행 신뢰 확인(Trust verification), 홈 디렉터리에서 신뢰가 저장되지 않는 동작과 프로젝트 하위 폴더 권장, -p 비대화형 실행에서 신뢰 확인이 동작하지 않는다는 설명, 수동 모드의 읽기 전용 시작과 작업 폴더 경계, 권한에 대한 사용자 책임

    공식 문서는 이 관문이 존재하는 이유와 저장 규칙까지는 설명하지만, 선택 항목의 순서나 커서 기본값은 적지 않는다. 그 부분은 아래의 우리 실측이다.

    우리가 직접 측정한 것 (2026-09-08, 공식 문서의 내용이 아니다)

    • 커서 기본값이 No, exit였고 아래 항목이 Yes, I trust this folder였다는 것. 우리 작업 폴더에는 도구 권한 두 건이 미리 허용돼 있었다는 조건을 함께 밝힌다. 그 사전 허용이 커서 배치의 원인인지는 코드로 확인하지 않았다
    • 폴더 신뢰 다음에 Bypass Permissions 면책 관문이 한 번 더 뜨고, 그 창에서도 엔터가 종료 쪽일 수 있다는 것
    • 승인 원장 82건 중 first_run_gate가 24건으로 최다였다는 집계
    • agents.jsonmeasured_on 계약 문면과, 그 값 2.1.241이 설치본 2.1.263과 달라 자동 통과가 보류된 상태였다는 것. gates는 빈 배열이었다
    • 관문 통과 뒤에도 데몬의 보류 표시가 남고 지우는 명령이 없다는 것. 다만 점검기가 각성 시각을 우선하도록 바뀌어 생존 판정에는 영향이 없다는 정정까지 함께 적는다

    커서 기본값은 환경에 따라 다를 수 있다. 이 글의 실측값을 자기 화면의 기본값으로 가정하지 말고, 화면을 직접 읽고 확정하기를 권한다.

  • Claude Code가 설치는 됐는데 안 켜질 때 — Windows 첫 실행 오류 전부 정리

    Claude Code가 설치는 됐는데 안 켜질 때 — Windows 첫 실행 오류 전부 정리

    설치는 끝났다고 나왔는데 claude를 쳤을 때 아무 일도 일어나지 않는다면, 대부분은 프로그램이 망가진 것이 아니다. 막힌 층이 어디인지만 가려내면 처방은 거의 정해져 있다. 이 글은 Windows에서 처음 실행할 때 나오는 오류를 증상 문구별로 모아 정리한 것이다. 앞의 여섯 항목은 공식 문서에서 확인한 내용이고 링크를 함께 달았다. 뒤의 세 항목은 우리가 이 컴퓨터에서 직접 겪은 것이라 공식 문서에는 없다. 어느 쪽인지 항목마다 밝혀 두었다.

    설치는 됐는데 안 켜지는 상황을 네 층으로 나눈 도식. 설치 명령 층, 경로 층, 셸 세션 층, 정책과 인증 층의 대표 오류 문구와 처방을 나란히 놓고, 아래 띠에 우리가 직접 겪은 세 가지를 적었다.
    막히는 층은 네 곳이다 — 증상 문구로 층을 먼저 가려낸다. 아래 띠는 우리가 2026년 9월에 직접 겪은 세 가지다.

    어떤 문구가 떴는가

    지금 화면에 나온 문구와 가장 비슷한 항목으로 바로 가면 된다. 초보일 때는 원인을 몰라도 괜찮다. 증상만 알면 충분하다.

    1. claude를 쳤는데 명령을 찾을 수 없다고 나온다

    증상. 화면에 command not found: claude 또는 'claude' is not recognized가 나온다. 설치는 분명히 끝났는데 명령만 없는 것처럼 보인다.

    왜 그런가. 공식 문서는 이렇게 설명한다. 설치는 됐지만 설치 폴더가 PATH에 없다는 뜻이고, 셸은 PATH에 적힌 폴더만 뒤져서 프로그램을 찾는다. Windows에서 설치 파일이 놓이는 자리는 %USERPROFILE%\.local\bin\claude.exe다.

    확인 명령. PowerShell에서 그 폴더가 PATH에 들어 있는지 본다.

    
    $env:PATH -split ';' | Select-String '\.local\\bin'
    

    처방. 위 명령이 아무것도 출력하지 않으면 PATH에 없는 것이다. 공식 문서가 안내하는 대로 사용자 PATH에 추가한다.

    
    $currentPath = [Environment]::GetEnvironmentVariable('PATH', 'User')
    [Environment]::SetEnvironmentVariable('PATH', "$currentPath;$env:USERPROFILE\.local\bin", 'User')
    

    공식 문서는 여기에 한 줄을 덧붙인다. “Restart your terminal for the change to take effect.” 즉 창을 다시 열어야 반영된다.

    확인 방법. 새 터미널 창을 열고 claude --version을 실행한다. 공식 문서 기준으로 정상 설치라면 2.1.211 (Claude Code)처럼 버전 번호가 찍힌다.

    한 가지 주의할 점이 문서에 따로 적혀 있다. VS Code 확장만 설치한 경우에는 claude가 그 자리에 놓이지 않는다. 확장은 자기 채팅 창을 위해 CLI 사본을 확장 폴더 안에 따로 두고 PATH에 추가하지 않기 때문이다. 터미널에서 claude를 쓰려면 별도 설치가 필요하다.

    2. 설치는 성공했다는데 새 창에서만 되거나, 옛 버전이 나온다

    증상. 설치 로그는 성공인데 방금 설치한 그 창에서만 claude를 못 찾는다. 또는 분명히 새로 설치했는데 옛 버전 번호가 찍힌다.

    왜 그런가. 공식 문서의 설명은 짧고 분명하다. “the session you installed from keeps its old PATH.” 설치 프로그램이 PATH를 바꿔도 이미 열려 있던 창은 예전 PATH를 그대로 들고 있다. 고객지원 문서도 Windows에서는 “Close and reopen PowerShell”이라고 안내한다.

    확인 명령. 지금 창에서 실패하는지, 새 창에서도 실패하는지를 나눠서 본다.

    
    claude --version
    

    처방. 창을 완전히 닫고 새 창을 열어 같은 명령을 다시 실행한다. 이것만으로 끝나는 경우가 많다.

    확인 방법. 새 창에서 버전 번호가 찍히면 해결된 것이다. 새 창에서도 여전히 못 찾으면 1번 항목의 PATH 문제로 넘어간다.

    3. 설치 명령 자체가 오류를 낸다

    증상. Claude Code를 켜기도 전에 설치 명령이 튕긴다. 공식 문서가 열거한 문구는 이렇다.

    
    'bash' is not recognized as the name of a cmdlet
    A parameter cannot be found that matches parameter name 'fsSL'
    The token '&&' is not valid
    'irm' is not recognized
    

    왜 그런가. 공식 문서는 이 네 가지를 모두 같은 원인으로 묶는다. 다른 셸이나 다른 운영체제용 설치 명령을 복사해 온 것이다. 예를 들어 curl -fsSL ... | bash는 macOS와 Linux용인데, Windows PowerShell에서는 curlInvoke-WebRequest의 별칭이라서 -fsSL 같은 옵션을 거부한다.

    확인 명령. 내가 지금 어느 셸에 있는지부터 확인한다. 공식 문서가 알려주는 구분법은 프롬프트 모양이다. “Your prompt shows PS C:\ when you’re in PowerShell and C:\ without the PS when you’re in CMD.”

    처방. 셸에 맞는 설치 명령을 쓴다. 공식 문서에 적힌 그대로다.

    
    PowerShell:  irm https://claude.ai/install.ps1 | iex
    CMD:         curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd
    WinGet:      winget install Anthropic.ClaudeCode
    

    설치 명령을 쳤는데 스크립트 내용만 화면에 쭉 출력되고 아무것도 설치되지 않는 경우도 있다. 공식 문서는 이것을 명령의 앞부분만 실행한 상태로 설명한다. irm https://claude.ai/install.ps1만 쓰면 내려받은 스크립트를 화면에 인쇄하고 끝난다. 실행까지 하려면 | iex를 붙여야 한다.

    확인 방법. 새 터미널을 열고 claude --version으로 버전 번호를 확인한다.

    4. Claude Code does not support 32-bit Windows가 나온다

    증상. 64비트 컴퓨터를 쓰는데도 32비트를 지원하지 않는다는 오류가 나온다.

    왜 그런가. 공식 문서의 설명은 이렇다. 시작 메뉴에는 PowerShell 항목이 두 개 있다. Windows PowerShellWindows PowerShell (x86)이다. x86 항목은 32비트 프로세스로 실행되며, 문서 표현대로 “triggers this error even on a 64-bit machine”이다. 즉 컴퓨터가 아니라 창을 잘못 연 것이다.

    확인 명령. 오류가 난 바로 그 창에서 실행한다.

    
    [Environment]::Is64BitOperatingSystem
    

    처방. True가 나오면 운영체제는 문제가 없다. 그 창을 닫고 (x86)이 붙지 않은 Windows PowerShell을 열어 설치 명령을 다시 실행한다. False가 나오면 32비트 Windows를 쓰는 것이고, 이 경우에는 64비트 운영체제가 필요하다. 공식 시스템 요건은 Windows 10 1809 이상 또는 Windows Server 2019 이상, 4GB 이상 메모리, x64 또는 ARM64 프로세서다.

    확인 방법. x86이 아닌 창에서 설치한 뒤 claude --version으로 버전 번호를 확인한다.

    5. running scripts is disabled on this system이 나온다

    증상. npm으로 설치했거나 npm으로 설치한 claude를 실행할 때 보안 오류가 난다. 공식 문서가 실은 원문은 이렇다.

    
    npm : File C:\Program Files\nodejs\npm.ps1 cannot be loaded because running scripts
    is disabled on this system.
        + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    

    원문 메시지 끝에는 마이크로소프트의 실행 정책 문서를 가리키는 주소가 한 줄 더 붙는다. 위 인용에서는 그 주소만 덜어냈고, 같은 문서를 이 글 맨 아래 출처에 링크해 두었다.

    왜 그런가. 공식 문서는 원인을 이렇게 짚는다. PowerShell의 실행 정책이 npm이 만들어 놓은 .ps1 실행 스크립트를 막고 있는 것이다. 같은 오류가 claude.ps1 이름으로도 나온다. 문서는 이 정책이 스크립트 파일에만 적용되므로 PowerShell 설치 명령 irm https://claude.ai/install.ps1 | iex에는 영향을 주지 않는다고 덧붙인다. 내려받은 글자를 바로 실행하는 방식이라 그렇다.

    실행 정책이 무엇인지는 마이크로소프트 공식 문서에 정의돼 있다. 문서는 실행 정책을 “a safety feature that controls the conditions under which PowerShell loads configuration files and runs scripts”라고 설명한다. 이 글에서 쓰는 RemoteSigned는 문서 표현으로 “Requires a digital signature from a trusted publisher on scripts and configuration files that are downloaded from the internet”이고 “Doesn’t require digital signatures on scripts that are written on the local computer and not downloaded from the internet”이다. 즉 인터넷에서 받은 스크립트만 서명을 요구하고, 내 컴퓨터에서 만들어진 스크립트는 그대로 실행한다.

    한 가지 헷갈리기 쉬운 대목이 있어 그대로 옮긴다. 같은 문서는 RemoteSigned를 “The default execution policy for Windows computers”라고 적으면서, 동시에 “If no execution policy is set in any scope, the effective execution policy is Restricted, which is the default for Windows clients”라고도 적는다. 두 문장이 함께 있으니, 내 컴퓨터가 지금 어떤 상태인지는 추측하지 말고 직접 확인하는 편이 정확하다.

    확인 명령. 현재 적용 중인 정책을 범위별로 본다.

    
    Get-ExecutionPolicy -List
    

    처방. 공식 문서가 제시하는 방법은 세 가지다. 첫째, 내 계정 범위에서 로컬 스크립트를 허용한다.

    
    Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
    

    둘째, .ps1 대신 .cmd 실행 파일을 부른다. 문서 설명대로 npm.cmdclaude.cmd는 같은 일을 하고 정책 적용 대상이 아니다. 셋째, npm 대신 PowerShell 설치 명령을 쓴다. 스크립트가 아니라 실행 파일을 설치하는 방식이다. 마이크로소프트 문서는 Set-ExecutionPolicy의 변경이 즉시 적용되며 PowerShell을 다시 시작할 필요가 없다고 적는다.

    확인 방법. Get-ExecutionPolicy -Scope CurrentUserRemoteSigned를 돌려주는지 보고, 이어서 claude --version으로 버전 번호를 확인한다.

    6. 켜지기는 하는데 로그인이 안 된다

    증상. claude는 켜지는데 로그인 단계에서 멈춘다. 브라우저가 저절로 열리지 않거나, OAuth error: Invalid code가 나오거나, 로그인 뒤에 403 Forbidden이 나온다.

    먼저 확인할 것. 공식 문서는 계정 조건을 명확히 적어 두었다. Claude Code는 Pro, Max, Team, Enterprise, Console 계정이 필요하고 “The free Claude.ai plan does not include Claude Code access.” 무료 요금제로는 접근이 되지 않으니, 설치 문제로 오래 헤매기 전에 계정 종류부터 확인하는 편이 빠르다.

    확인 명령. Claude Code 안에서 현재 어떤 인증 방식이 쓰이는지 본다.

    
    /status
    

    처방. 공식 문서가 안내하는 순서는 이렇다. 원인이 분명하지 않을 때는 로그인을 깨끗하게 다시 한다. /logout으로 완전히 로그아웃하고, Claude Code를 닫고, claude로 다시 시작해 인증을 처음부터 진행한다. 브라우저가 저절로 열리지 않으면 c를 눌러 인증 주소를 복사한 뒤 브라우저에 직접 붙여 넣는다. 터미널이 좁아 주소가 여러 줄로 잘려 클릭이 안 될 때도 같은 방법을 쓴다.

    OAuth error: Invalid code. Please make sure the full code was copied가 나오는 경우, 문서는 코드가 만료됐거나 복사 과정에서 잘렸다고 설명한다. 브라우저가 열린 뒤 지체 없이 로그인을 마치는 것이 처방이다.

    로그인은 됐는데 403 Forbidden이 나오는 경우에는 환경 변수를 본다. 문서 설명대로 ANTHROPIC_API_KEY가 설정돼 있고 그 키를 승인해 둔 상태라면, Claude Code는 구독 계정의 인증 대신 그 키를 쓴다. Windows에서는 PowerShell 프로필($PROFILE)과 사용자 환경 변수에서 ANTHROPIC_API_KEY를 확인하라고 안내한다.

    확인 방법. /status로 지금 활성화된 인증 방식이 의도한 것과 같은지 확인한다.

    7. 점검 도구가 한글 콘솔에서 통째로 멈춘다

    여기서부터 세 항목은 공식 문서에 없다. 우리가 이 컴퓨터에서 직접 겪은 것이다.

    증상. 설치 상태를 점검하려고 돌린 진단 스크립트가 아래 오류를 내고 그 자리에서 죽었다. 점검 결과를 한 줄도 못 봤다.

    
    UnicodeEncodeError: 'cp949' codec can't encode character '\u2014'
    

    무엇이 문제였나. 정확히 밝혀 둔다. 죽은 것은 Claude Code 본체가 아니라 우리가 만든 파이썬 진단 스크립트였다. 원인은 프로그램 논리가 아니라 글자였다. 한국어 Windows 콘솔의 기본 인코딩은 cp949이고, 여기에는 줄표(U+2014, em dash)를 표현할 자리가 없다. 진단 결과를 출력하는 문장에 줄표가 한 글자 들어 있었고, 그 한 글자를 인코딩하는 순간 예외가 나면서 진단이 통째로 중단됐다. 설치 상태는 아무 문제가 없었다.

    확인 명령. 지금 파이썬이 어떤 인코딩으로 출력하는지 본다.

    
    python -c "import sys; print(sys.stdout.encoding)"
    

    처방. 우리가 실제로 쓴 방법은 출력 인코딩을 UTF-8로 고정하는 것이었다. 파이썬은 환경 변수로 이것을 지정할 수 있다.

    
    $env:PYTHONUTF8 = "1"
    $env:PYTHONIOENCODING = "utf-8"
    

    덧붙이자면, 겪고 나서 우리가 코드에 남긴 교훈은 다른 쪽이었다. 진단 도구의 출력 문장은 예쁜 기호를 쓰지 않는 편이 낫다는 것이다. 줄표 대신 붙임표를 쓰면 이 고장 자체가 생기지 않는다. 도구가 멈추면 정작 확인하려던 사실을 아무것도 못 보게 된다.

    확인 방법. 같은 스크립트를 다시 돌려 끝까지 출력이 나오는지 본다. 위 확인 명령이 utf-8을 돌려주면 인코딩은 잡힌 것이다.

    8. 분명히 설치했는데 자동 점검이 미설치라고 한다

    증상. 파일 탐색기로 확인하면 실행 파일이 그 자리에 분명히 있다. 그런데 우리가 만든 자동 점검이 그 CLI를 미설치로 판정했다.

    무엇이 문제였나. 실행 파일은 실재했고, 문제는 점검을 수행한 쪽의 PATH였다. 우리 자동 점검은 별도 프로세스를 띄워서 명령을 찾는데, 그 프로세스가 물려받은 PATH에 해당 폴더가 들어 있지 않았다. 그래서 파일이 있는데도 없다고 보고했다. 사람이 직접 창을 열어 실행하면 되는데 자동 점검만 실패하는 상태였고, 우리는 이 차이를 알아채기까지 시간을 썼다.

    이 오판정은 1번 항목과 뿌리가 같다. 셸이든 자동화든 프로그램을 찾는 방법은 PATH뿐이라, PATH를 물려받지 못한 프로세스에게는 그 파일이 존재하지 않는 것과 같다. 다른 점은 하나다. 1번은 사람이 오류 문구를 보고, 8번은 자동화가 조용히 잘못된 결론을 남긴다.

    확인 명령. 지금 이 창에서 찾을 수 있는지, 그리고 파일이 실재하는지를 따로 확인한다. 둘의 답이 갈리면 PATH 문제다.

    
    where.exe claude
    Test-Path "$env:USERPROFILE\.local\bin\claude.exe"
    

    처방. 파일은 있는데 where.exe가 못 찾으면 1번 항목의 PATH 추가를 적용한다. 자동화 쪽이라면 자동화가 띄우는 프로세스의 PATH에 그 폴더가 들어가는지를 따로 확인해야 한다. 사람이 쓰는 창에서만 고쳐 놓으면 자동 점검은 계속 미설치라고 답한다.

    확인 방법. where.exe claude가 경로를 출력하고, 자동 점검을 다시 돌렸을 때 같은 결론이 나오는지 본다. 사람과 자동화의 답이 일치하는 상태가 정상이다.

    9. 어제까지 되던 명령이 셸을 바꾸니 사라졌다

    증상. 잘 쓰던 CLI가 어느 날 없는 명령이 됐다. 우리 경우에는 셸을 바꿔서 새로 열었을 때였다.

    무엇이 문제였나. 정확히 적어 둔다. 이때 사라진 것은 Claude Code가 아니라, 같은 컴퓨터에 npm 전역 설치로 넣어 둔 다른 CLI였다. 그 컴퓨터에는 Node 버전 관리자(fnm)가 깔려 있었고, npm 전역 설치는 버전 관리자가 셸마다 만들어 주는 경로 아래로 들어갔다. 그 경로는 셸 세션에 딸린 임시 경로여서, 셸이 바뀌자 그대로 사라졌다. 파일이 지워진 것이 아니라 그 경로를 아무도 안 보게 된 것이다.

    처방. 우리가 쓴 해법은 셸에 딸리지 않는 안정된 절대경로에 실행 지점을 고정하는 것이었다. 구체적으로는 ~/.local/bin 아래에 작은 실행 파일을 두고 그것을 부르게 했다. 그 뒤로는 셸이 바뀌어도 같은 자리에서 실행된다.

    Claude Code에는 이 문제가 애초에 덜 생긴다. 공식 문서에 따르면 기본 설치 방식은 %USERPROFILE%\.local\bin\claude.exe라는 고정된 자리에 실행 파일을 놓기 때문이다. npm 전역 설치를 택할 때만 위와 같은 상황을 고려하면 된다. 참고로 공식 문서는 npm 설치에 대해 “Do NOT use sudo npm install -g“라고 경고하고, npm 패키지도 결국 같은 네이티브 실행 파일을 설치한다고 설명한다.

    확인 방법. 셸을 바꿔 새 창을 열고 where.exe claude가 같은 경로를 출력하는지 본다. 셸에 따라 결과가 달라지면 아직 휘발성 경로에 물려 있는 것이다.

    정리

    아홉 가지를 봤지만 실제로 층은 네 개다. 설치 명령을 셸에 맞게 골랐는가, 설치 폴더가 PATH에 있는가, 지금 창이 새 PATH를 들고 있는가, 정책과 계정이 막고 있지는 않은가. 이 네 가지를 순서대로 짚으면 대부분 끝난다.

    마지막으로 점검 명령 하나를 적어 둔다. 공식 문서는 설치와 설정을 자세히 보려면 claude doctor를 실행하라고 안내한다. 문서 설명으로는 세션을 시작하지 않고 읽기 전용 진단만 출력하며, 설치 상태와 설정 파일 오류, 권장 조치가 함께 나온다.

    
    claude doctor
    

    우리가 겪은 세 가지에서 남은 교훈은 하나로 모인다. 프로그램이 없다는 메시지는 대체로 프로그램이 없다는 뜻이 아니고, 그것을 찾는 쪽이 어디를 보는지의 문제였다. 그리고 점검 도구는 자기가 먼저 멈추지 않아야 쓸모가 있다.

    출처

    7번부터 9번까지는 2026년 9월에 우리 환경에서 직접 확인한 것이며 공식 문서의 내용이 아니다. 나머지 항목의 문구와 명령은 위 출처에서 인용했다.