
자작 린터를 230개 리포지토리에 적용했더니 80%가 오탐(False Positive)이었다
요약
AI 에이전트용 지시서(SKILL.md 등)의 환경 의존성을 검사하는 린터 'carrylint'를 개발하고 230개 리포지토리에 적용한 실험 결과입니다. 분석 결과, 실제 버그보다 관례적인 표현(~/ 경로, 플레이스홀더 등)을 오류로 판단하는 오탐률이 80%에 달함을 확인했습니다.
핵심 포인트
- AI 에이전트 지시서 내 개인 경로 포함 여부를 검사하는 린터 개발
- 230개 실제 리포지토리 적용 결과 80%가 오탐(False Positive) 발생
- 홈 디렉토리 기호(~)와 플레이스홀더를 오류로 인식하는 한계 발견
- 도구의 규칙이 개발자들의 관례(Convention)와 충돌할 수 있음을 시사
AI 에이전트에게 전달하는 지시서, SKILL.md
혹은 AGENTS.md
혹은 CLAUDE.md
같은 것들. 그런 것을 사람들에게 배포해 본 적이 있는 사람이라면 아마 한 번쯤은 겪어봤을 것입니다. 자신의 컴퓨터에서는 완벽하게 작동했는데, 전달받은 상대방의 환경에서는 작동하지 않는 경우 말입니다.
대개 원인은 정해져 있습니다. 작성한 본인의 환경이 본문에 박혀 있는 것이죠. /Users/본인이름/
으로 시작하는 경로. 설치 절차 어디에도 적혀 있지 않은 명령어. <FILL_ME>
상태로 남겨진 플레이스홀더(Placeholder). 자신의 화면에서는 전부 제대로 연결되기 때문에 마지막까지 눈치채지 못합니다.
그것을 CI에서 걸러내는 린터(Linter)를 만들었습니다. carrylint라고 합니다. 내부에서 AI는 사용하지 않고, 텍스트를 읽어서 찾아낼 뿐인 도구입니다. 7월 22일에 공개했습니다.
그런데 공개한 다음 날, 문득 손이 멈췄습니다. '이걸 다른 사람의 진짜 파일들에 적용하면 뭐라고 할까'라는 생각이 들었습니다.
우선 230개를 모아왔다
GitHub의 코드 검색을 통해 공개되어 있는 SKILL.md와 AGENTS.md를 끈질기게 수집했습니다. 조정용으로 160개. 나중에 정답을 확인하기 위해 절대 건드리지 않고 따로 떼어둔 70개. 합쳐서 230개입니다. 전부 타인이 작성하고 실제로 사용되고 있는 파일들입니다.
먼저 160개에 당시의 carrylint(v0.1.0)를 적용했습니다.
ERROR 108건 (29개 파일)
WARN 364건
29개 파일. 이때는 '꽤 많이 잡아냈네' 정도의 기분이었습니다.
발견된 버그는 확실히 진짜였다
먼저 좋은 이야기부터 하겠습니다. 목표로 했던 문제는 분명히 있었습니다.
op7418/guizang-ppt-skill
/Users/guohao/Documents/...
godavidgpg/game-design-analyzer-upstage
...
guohao 님, vudrk 님, fatherlinux 님, metal 님, CS 님. 이름이 다르면 작동하지 않습니다. godavidgpg 님의 경우에는 절차의 단계가 전부 cd "C:/Users/vudrk/Desktop/AI Projects"
에서 시작되어 있어서, 타인이 설치하면 7번 연속으로 헛스윙을 하게 됩니다. libthumbor 쪽은 원본인 thumbor(★1만 개 이상)와 같은 곳에서 운영하는 리포지토리인데, AGENTS.md가 "이것을 보세요"라고 안내하는 설정 파일 링크 4개 모두가 작성자의 홈 디렉토리를 가리키고 있었습니다.
여기까지는 '만들길 잘했다'는 이야기입니다.
문제는 그 이후였다
ERROR의 내용을 하나씩 살펴보기 시작했습니다. 통계를 훑어보는 것이 아니라, 중복을 제외한 61개 행을 전부 눈으로 직접 확인했습니다. 이것이 정말 버그인지, 아니면 내가 트집을 잡고 있는 것인지 확인하기 위해서였습니다.
진짜 12건
오탐(False Positive) 49건
80%가 오탐이었습니다.
파일 단위로 계산하면 더 심각합니다. ERROR로 걸러낸 29개 파일 중 정말로 수정해야 할 것이 포함된 것은 5개 파일뿐이었습니다. 나머지 24개 파일은 아무런 잘못도 하지 않았습니다.
내역을 살펴보던 도중, 점점 할 말을 잃게 되었습니다.
우선 ~/를 341건이나 경고했습니다. 가장 많았던 것이 이것입니다. ~/.claude/skills/... 같은 홈 상대 경로에 대해 "사용자의 홈을 전제로 합니다"라고 말했는데, ~는 각각의 환경에서 각자의 홈으로 해석됩니다. 즉, 나는 가장 제대로 된 작성 방식에 불만을 제기하고 있었던 셈입니다. 341건 전부 틀렸습니다.
YOUR_API_KEY와 /path/to/...는 "미해결 플레이스홀더"로 취급했습니다. 이것은 "이 부분을 당신의 값으로 교체해 주세요"라는 작성 관례(Convention)이지, 적는 것을 잊어버린 것이 아닙니다. 26건.
claude mcp add ...는 "선언되지 않은 CLI"라며 걸러냈습니다. 스킬을 실행하는 당사자에게 그 CLI를 의존성(Dependency)으로 작성하라고 말하고 있었던 셈입니다. 13건.
$HOME도 절대 경로로 취급했습니다. ~와 마찬가지로 이것 역시 환경을 넘나들 수 있는 작성 방식입니다.
나열해 놓고 보니 깨달았습니다. 내 도구가 공격하고 있었던 것은 관례를 모르는 사람이 아니라, 오히려 관례대로 작성하고 있는 사람들이었습니다. ~를 사용하고, YOUR_API_KEY라고 적고, 호스트의 명령어를 안내한다. 제대로 된 사람일수록 내 CI에서 걸러진다. 최악이라고 생각했습니다.
테스트는 40건, 전부 통과(Green)였습니다
이때 제 수중에 있던 테스트는 40건이었고, 전부 통과하고 있었습니다. 제가 직접 작성한 examples/good은 제대로 통과하고, examples/bad는 제대로 실패했습니다. CI(Continuous Integration)도 초록색(Green)이었죠. 그런데 결과는 80%의 오탐(False Positive)이었습니다.
지금은 알 수 있지만, 제가 직접 작성한 테스트에는 제가 상상한 입력값만 들어있었습니다. 상상하지 못한 작성 방식은 애초에 테스트 케이스가 될 수조차 없었던 것입니다. 이렇게 말하면 당연한 소리 같지만, 초록색 체크 표시가 나열되어 있으면 그것이 마치 정답의 증명처럼 보이게 됩니다.
게다가 이때는 이미 npm에 공개한 상태였고, Zenn과 Qiita에도 글을 올린 후였습니다. 홍보를 시작하기 전이었던 것은 그저 운이 좋았을 뿐이라고 생각합니다. 만약 미리 공지했다면, 재미 삼아 도입해 준 사람들의 첫 번째 시도에서 4번 중 5번은 억울한 누명을 씌웠을 것입니다.
린터(Linter)가 단 한 번이라도 잘못하여 사람의 PR(Pull Request)을 떨어뜨리면, 다음부터는 --no-verify로 제외됩니다. 거기서 끝입니다.
수정했습니다
수정한 것은 4가지입니다.
・$HOME / ~ / %USERPROFILE%은 환경을 넘나들 수 있으므로 더 이상 보지 않는다. 실제 사용자 이름이 포함된 절대 경로(/Users/<이름>/)만 ERROR로 처리한다. /home/user와 같은 명백한 예시도 제외한다.
・플레이스홀더(Placeholder)는 작성 중임을 나타내는 표식(<FILL_ME, <REPLACE_ME, <CHANGEME)으로만 한정한다. YOUR_API_KEY와 /path/to/는 올바른 관례이므로 제외한다.
・호스트의 셋업 관련 명령어(claude mcp add, codex mcp add, claude --version)는 제외한다. 이 규칙 자체도 ERROR에서 WARN(Warning)으로 낮추어 PR을 중단시키지 않도록 한다.
・~/를 확인하는 규칙은 통째로 삭제한다.
동일한 160개의 리포지토리에 수정된 v0.1.1을 다시 적용했습니다.
ERROR 14건 (5개 파일)
29개 파일이 5개 파일로 줄었습니다. 남은 5개 파일은 아까 언급한 guohao 님 등의 파일 그대로입니다. 오탐만 사라졌을 뿐, 진짜 문제는 단 하나도 놓치지 않았습니다.
가장 안심했던 점은, 조정 과정에서 한 번도 사용하지 않은 나머지 70개의 리포지토리에서도 동일한 결과가 나왔다는 것입니다. 만약 여기서 결과가 달랐다면, 160개에만 편의상 맞춘 도구가 되었을 것이고, 수정할 의미가 없었을 것입니다.
마지막으로, 이번 감사에서 발견한 실제 사례를 그대로 테스트로 만들었습니다. 계속해서 탐지해주길 바라는 진짜 사례 5건과, 두 번 다시 울리지 말아야 할 실제 패턴 11건. 타인의 리포지토리에서 빌려온 16줄의 코드가 현재 제가 가장 신뢰하는 테스트입니다.
AI와 함께 만들었기에 더욱 그랬다
carrylint는 대부분 AI와 함께 작성했습니다. 빠르고, 테스트도 함께 작성해 줍니다. 하지만 테스트의 상상력은 구현의 상상력이 닿는 범위까지만 미칩니다. 동일한 편견이 구현과 테스트 양쪽에 들어가기 때문에, 내부만 보고 있으면 어디까지나 앞뒤가 맞아떨어지게 됩니다. 외부에서 진짜 사례를 가져오기 전까지는 아무것도 알 수 없었습니다.
제가 준비한 examples/bad 역시 결국 제가 상상한 잘못된 작성 방식의 견본일 뿐이었습니다. 실제 타인의 파일에는 제가 생각지도 못한 '올바른 작성 방식'이 산더미처럼 많았고, 저는 그것들을 하나하나 때리고 있었습니다.
홍보하기 전 30분
사람들에게 배포할 것을 만들고 있다면, 공지하기 전에 딱 30분만 투자해 보세요. 할 일은 세 가지입니다.
- GitHub 코드 검색을 통해 해당 도구가 대상으로 삼는 파일을 100개 정도 모은다 (
gh search code면 충분합니다). - 한꺼번에 실행한다.
- 나온 결과를 샘플링하지 말고 전부 눈으로 확인한다.
세 번째가 핵심입니다. 저는 61건을 하나씩 확인했습니다. 통계치만 봤다면 "29개 파일에서 탐지, 나쁘지 않음"이라며 기분 좋게 끝냈을 것이고, 그중 24건이 억울한 누명이었다는 사실은 평생 깨닫지 못했을 것입니다.
80%라는 숫자는 솔직히 꽤 충격적이었습니다. 하지만 이 사실을 홍보 전에 알게 되어 다행이라는 이야기이기도 합니다.
carrylint는 지금도 수정하며 사용하고 있습니다. 다른 도구들도 6개 정도 배포해 두었기에, 같은 과정을 순차적으로 진행할 생각입니다. 아마 또다시 같은 경험을 하게 될 것 같습니다.
Discussion

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