콘텐츠로 이동

검증에 떨어지는 mod

기준: 커뮤니티 카탈로그 2026-10-04 스캔, Claude Code 2.1.289

awesome-claude-code-mods 카탈로그는 찾은 mod마다 claude plugin validate를 돌리고 결과를 저장해요. 이 글은 그 결과 1,919건을 모두 읽었어요. 디렉터리에 오르지 못한 항목이 왜 떨어졌는지 보여 주는 자료라서, mod를 만드는 사람에게 쓸모가 있어요.

종류 통과 경고 실패 알 수 없음
mod 1,485 224 31 12
fixture (시험용 예제) 62 16 12 3
catalog 38 1 0 0
duplicate 15 11 2 0
builtin 3 0 1 0
mirror 2 0 1 0
합계 1,605 252 47 15

“알 수 없음” 15건은 검증에 떨어진 것이 아니에요. 11건은 저장소를 읽지 못해 지난번 결과를 보여 주는 경우이고, 4건은 이번 스캔에서 찾지 못한 경우예요.

이 사이트의 mod 디렉터리는 처음에 “통과”만 실었다가, 10월 6일부터 경고와 함께 통과한 일반 mod 224개도 “검증 경고” 표시를 달아 실어요. 실패 31개는 빠져요. 아래에서 보듯 경고는 대부분 고치기 쉬운 것이에요.

오류 문구의 첫 원인으로 나눴어요.

원인 건수 그중 일반 mod 예
예약된 이름 (claude-로 시작) 19 15 claude-council, claude-stats
hooks.json이 가리키는 모듈 파일 없음 6 5 self-improvement-loop
가져오는 파일 없음·폴더 밖 5 3 jev-context, persona-panel
상태 계약에 없는 키 3 3 wavy-usage, terminal-gym
anthropic 텔레메트리 스트림 3 0 내장 telemetry와 그 사본
구문 오류 3 1 agents-skills
on() 반환값을 변수에 담음 2 2 shunt
없는 이벤트 이름 2 0 시험용 예제
$.env.get에 변수 전달 1 1 fast-jev-compaction의 한 사본
매니페스트 없음 1 1 pilot-guard
modules에 두 개 1 0 시험용 예제
userConfig 필드 누락 1 0 시험용 예제

시험용 예제 12건은 일부러 틀리게 만든 것이 많아요. 일반 mod 31건만 보면 예약된 이름이 15건으로 절반 가까이 돼요.

19건이 같은 오류를 받았어요. 검증기 문구 그대로예요.

Plugin name "claude-council" is reserved: it passes as one of Anthropic's own.
A third party's plugin name cannot start with "claude-", "anthropic-", "anthropics-",
or "cc-plugin-", be "claude", "anthropic", "anthropics", "claude-code", or
"claude-mods", or put "official" beside "claude" or "anthropic". Name it for what
it does.

claude-cat, claude-queue, claude-mermaid, claude-games, claude-link, claude-who, claude-slots도 같은 이유로 떨어졌어요. 19건 중 17건은 이 오류 하나만 받았어요. 이름 중간에 claude가 들어간 경우는 실패하지 않고 경고를 받기도 해요. 2.1.291로 다시 돌려 보니 mindful-claude는 “reads as one of Anthropic’s own”이라는 경고를 받았어요.

hooks/hooks.json의 modules가 가리키는 파일이 저장소에 없어서 떨어진 경우가 6건이에요. 두 건은 dist/ 아래 빌드 결과를 가리켰어요(dist/hook-module.js, dist/integrations/claude.js). 빌드 결과를 커밋하지 않으면 설치한 사람에게도 파일이 없어요. 모듈은 .ts나 .tsx 원본을 그대로 가리키면 돼요. 엔진이 직접 읽어요.

import가 실패한 5건 중 2건은 파일은 있는데 mod 폴더 밖이었어요.

cannot import "../../../character-core/src/index.ts" (from hooks/function/register.js):
it is outside the plugin's folder (packages/persona-panel)

모노레포에서 공용 패키지를 상대 경로로 끌어오면 이렇게 돼요. 검증기는 mod 폴더 밖의 파일을 가져오지 못하게 막아요. 공용 코드는 mod 폴더 안에 복사해 둬요.

plugin.json에 "types"로 상태 계약 파일을 둔 mod는 $.state로 쓰는 키를 모두 그 파일의 PluginState에 적어야 해요. terminal-gym은 키 8개를 빠뜨려 오류 8줄을 받았어요.

terminal-gym.history is not declared: the manifest's types contract must name it
in interface PluginState { terminal-gym: { history: ... } }

anthropics/claude-code 저장소의 내장 telemetry mod도 실패로 나와요.

its hooks stand on the telemetry stream "anthropic" (a matcher names it, or a
telemetry hook names no "to" and so stands on every stream), which is for the
plugins built into the CLI; name the collector on each telemetry hook:
on("telemetry.log", { to: "collector" }, hook)

폴더로 검증하면 검증기는 이것을 바깥 mod로 봐요. anthropic 스트림은 CLI에 들어 있는 플러그인 몫이라 거절해요. 내 mod가 텔레메트리 훅을 건다면 { to: "collector" }를 꼭 적어요. to를 빼면 모든 스트림에 걸린 것으로 봐요.

검증기는 소스를 정해진 모양으로 읽어요. 그 모양에서 벗어나면 실행 전에 떨어져요.

규칙 떨어진 코드 고친 모양
on()의 반환값은 그 자리에서 .catch만 받아요 const registration = on("command.run", ...) on(...).catch(handler)
$.env.get은 문자열 리터럴만 받아요 $.env.get(name) $.env.get("TYPESAFE_API_KEY")
이벤트 이름은 정확해야 해요 on("classic.SessionStartt", ...) on("classic.SessionStart", ...)
turn.step 훅은 async generator여야 해요 async ($, e, next) => ... async function* ($, e, next) { ... }
modules에는 모듈 하나만 둬요 두 번째 항목 진입 모듈 하나에서 나머지를 import

turn.step 규칙은 claude-slots가 이름 오류와 함께 받은 두 번째 오류예요. agents-skills는 hooks/discover/scan.ts 89행의 정규식 리터럴을 파서가 읽지 못해 떨어졌어요.

경고로 끝난 항목은 252건이에요. 일반 mod가 224건이에요. 카탈로그는 경고 문구를 저장하지 않아요. 그래서 경고 상태 mod 43개(저장소 12곳)를 얕게 받아 Claude Code 2.1.291의 claude plugin validate로 다시 읽혔어요. 검증기는 소스만 읽고 mod를 실행하지 않아요. 43개 모두 다시 “passed with warnings”가 나왔어요. 한 mod가 경고를 여러 개 받기도 해요.

경고 43개 중
author: No author information provided. Consider adding author details for plugin attribution 40
root: CLAUDE.md at the plugin root is not loaded as project context. 2
이름이 Anthropic 것처럼 읽힘 1
명령 훅의 ${CLAUDE_PLUGIN_ROOT}에 따옴표 없음 1 (14줄)
심볼릭 링크를 따라가지 않고 읽음 1

카탈로그 전체로 봐도 같은 그림이에요. 경고 252건 중 169건은 작성자 칸이 비어 있어요. 통과한 1,605건 중에서는 1건뿐이에요. plugin.json에 author 한 줄이 없어서 경고를 받는 mod가 많다는 뜻이에요.

검증 출력에는 gating hook without .catch: 줄도 자주 보여요. 이것은 경고가 아니에요. 거절할 수 있는 자리(tool.call, prompt.submit 같은)에 건 훅에 .catch가 없다는 사실만 알려 줘요. 그런 훅은 오류가 나면 건너뛰어지고 요청이 그대로 통과해요. 막는 것이 목적인 훅이라면 .catch를 달아요.

카탈로그는 검증기와 별도로 자체 검사를 하나 더 해요. ui-control-characters 경고예요. 전체 184건, 디렉터리에 실린 mod 중 152건이 받았어요.

UI hook source contains control-character strings. Review any values passed to
next() as rewritten props.text; the scanner does not trace whether these strings
reach that call.

카탈로그 스캐너 소스(tools/compatibility.mjs)를 읽어 보면 규칙은 단순해요. ui.render를 거는 mod의 훅 모듈과 그 모듈이 상대 경로로 가져오는 파일에서, 문자열 리터럴에 U+0000–0008, U+000B–001F, U+007F–009F 문자가 있으면 표시해요. 탭과 줄바꿈은 빼요. 그 문자열이 화면까지 가는지는 따지지 않아요.

그래서 실제로 화면과 상관없는 코드도 걸려요. 직접 열어 본 사례예요.

mod 걸린 문자열 쓰임
diff-viewer '\u0000' 바이너리 파일 판별
gb-pane '\u0001F ' 내부 메시지 구분 표시
data-peek '\r' CSV 줄 끝 처리
내장 diff git/parse/ 아래 여러 줄 git 출력 파싱

이 경고가 정말 문제가 되는 경우는 ANSI 색 코드(\x1b[31m) 같은 문자열을 엔진에 넘길 때예요. 타입 파일은 Code의 source와 Markdown의 text에 탭과 줄바꿈만 제어 문자로 허용하고, 창 title의 제어 문자는 거절한다고 적어요. 색은 Text의 color, bold 같은 속성으로 줘요. 터미널 출력을 그대로 보여 줘야 한다면 제어 문자를 지운 뒤 넘겨요.

점검 피하는 실패
이름이 claude-, anthropic-, cc-plugin-으로 시작하지 않고, 하는 일을 말해요 예약된 이름
plugin.json에 author가 있어요 경고
hooks.json의 modules가 커밋된 원본 파일 하나를 가리켜요 모듈 파일 없음
import하는 파일이 모두 mod 폴더 안에 있어요 폴더 밖 가져오기
$.state 키를 계약 파일의 PluginState에 모두 적었어요 상태 계약
$.env.get("이름")처럼 리터럴로 써요 변수 전달
on(...)에 .catch를 바로 이어 붙여요 반환값 보관
turn.step 훅은 async function*이에요 generator 규칙
텔레메트리 훅에 { to: "collector" }가 있어요 anthropic 스트림
명령 훅의 ${CLAUDE_PLUGIN_ROOT}를 따옴표로 감쌌어요 경고
화면에 넘기는 글에서 ANSI 코드를 지웠어요 제어 문자

올리기 전에 두 명령을 돌려요.

터미널 창
claude plugin validate ./my-mod
claude plugin test ./my-mod

validate는 매니페스트, 마켓플레이스 파일, 훅 모듈을 한 번에 읽고 거절할 것을 모두 알려 줘요. 경고까지 0이 되면 카탈로그와 이 사이트 디렉터리에서 경고 표시 없이 실려요. 배포 절차는 내 mod 배포하기에, 로드가 안 될 때 보는 곳은 문제 해결에 있어요.

비공식 커뮤니티 가이드입니다. Anthropic과 제휴하거나 승인받지 않았습니다. Claude와 Claude Code는 Anthropic의 상표입니다.