Claude Code 훅이 Hermes에서 조용히 실패 — 가드가 안 돈다

agentskills.io 스펙, Claude Code의 훅 확장, Hermes Agent의 오픈 런타임으로 실제 이식되는 스킬.

Claude Code 훅이 Hermes에서 조용히 실패 — 가드가 안 돈다
Share

Anthropic은 Agent Skills를 이식 가능한 형식으로 내세웠다. 도구에서 MCP가 약속했던 것처럼, 같은 SKILL.md가 호환되는 모든 플랫폼에서 실행되어야 한다는 것이다. 그 약속은 형식에는 맞지만, 고급 스킬이 실제로 의존하는 모든 것까지 보장하지는 않는다.

agentskills.io가 요구하는 것과 의도적으로 비워 둔 것

What agentskills.io mandates — and what it intentionally omits (source: platform.claude.com)

agentskills.io 표준은 의도적으로 최소한만 규정한다. YAML frontmatter에 정확히 두 개의 필수 필드, 즉 name(64자 제한)과 description(1024자 제한)을 담은 SKILL.md 파일 하나가 들어 있는 디렉터리만 요구한다 . 그 밖의 license, compatibility, metadata, 실험적인 allowed-tools, 그리고 scripts/, references/, assets/ 디렉터리는 모두 선택 사항이거나 구현체가 정하는 영역이다 . Anthropic은 Claude Code에 스킬이 처음 도입된 지 약 두 달 뒤인 2025년 12월 18일에 이 사양과 SDK를 공개했다.

이 표준은 세 단계의 점진적 공개 방식을 정의한다. 스킬 하나당 약 50~100토큰의 간결한 카탈로그 항목, 트리거 시 로드되는 전체 SKILL.md 본문, 그리고 필요할 때 가져오는 번들 리소스다. 활성화된 지침은 5000토큰 미만을 권장한다 . 중요한 점은 런타임 계약이 파일시스템 접근, bash, 코드 실행이라는 세 가지 기본 요소로 축소된다는 것이다. 공유 사양에는 hook ABI도, 권한 풀 변경도, 서브에이전트 fork 기본 요소도 없다.

Nous Research의 Hermes Agent는 MIT 라이선스의 모델 비종속 런타임으로, 이 세 단계를 모두 충족한다. 카탈로그 인덱스에는 skills_list()를, 전체 본문에는 skill_view(name)를 노출하고, 스킬은 ~/.hermes/skills/ 아래에 저장하며, local, Docker, SSH, Singularity, Modal, Daytona 등 여섯 가지 터미널 백엔드에서 실행한다 . 바로 이 때문에 단순한 스킬은 깔끔하게 이식된다. 그리고 다음 섹션의 하네스에 묶인 필드들은 그렇지 않은 이유도 여기에 있다.

PreToolUse, CLAUDE_* 환경 변수, fork-context: Hermes에서 조용히 사라지는 것들

Screenshot of https://code.claude.com/docs/en/hooks

Claude Code는 agentskills.io 표준을 따르지만, 공개 사양이 정의하지 않는 세 가지 기능 계층을 덧붙인다. 호출 제어, 서브에이전트 실행, 동적 컨텍스트 주입이다 . Hermes에서는 이런 확장이 인식되지 않는 frontmatter 키와 설정되지 않은 변수로 남는다. SKILL.md는 여전히 로드된다. 하지만 강제 적용, 격리, 사전 렌더링 보장은 사라지고, 그 사실을 알려 주는 경고도 없다.

함정은 침묵이다. 파괴적인 쿼리를 PreToolUse 매처 뒤에 두고 ./scripts/validate-readonly-query.sh를 실행하도록 만든 스킬은 Hermes에서도 문제없이 파싱된다. 하지만 콜백은 절대 실행되지 않는다. Hermes에는 Claude Code hook ABI가 없으므로, 그 매처는 그저 알 수 없는 키일 뿐이다 . 하네스에 있어야 했던 강제 적용은 조용히 모델에게 넘어간다. 필드별로 같은 일이 벌어진다.

Claude Code 확장목적Hermes 동작
hooks: (PreToolUse 등)모델 밖에서 결정적으로 도구 사용을 차단알 수 없는 frontmatter 키 — 콜백이 실행되지 않음
allowed-tools / disallowed-toolsBash(git *), Skill(name *) 같은 권한 문법읽기는 하지만 적용하지 않음 — 권한 표면이 완전히 열린 채 유지됨
context: fork + agent: Explore스킬 본문을 격리된 서브에이전트로 실행인식되지 않는 키 — 스킬이 인라인으로 실행되고, fork된 격리는 없음
CLAUDE_* 환경 변수세션/노력 수준/디렉터리를 subprocess에 주입Claude가 아닌 런타임에서는 설정되지 않음
인라인 !`cmd` 치환명령 출력을 본문에 사전 렌더링해결되지 않음 — 리터럴 마커 그대로 전달됨

권한 격차가 가장 날카로운 부분이다. Bash(git *), Skill(name *) 같은 Claude 전용 문법에는 이식 가능한 대응물이 없다. Hermes는 해당 키를 읽지만 게이트를 적용하지 않으므로, 읽기 도구만 허용하도록 작성된 스킬도 제한 없이 실행된다 . 컨텍스트 주입도 똑같이 조용히 깨진다. CLAUDE_SESSION_ID, CLAUDE_EFFORT, CLAUDE_SKILL_DIR, CLAUDE_PROJECT_DIR는 빈 문자열로 해석된다. 마지막 변수는 Claude Code v2.1.196 이상이 필요하다 . 그리고 인라인 !`git diff HEAD` 블록은 실행되지 않은 채 모델 컨텍스트에 들어간다. 스킬은 멀쩡해 보이지만, 그것을 안전하게 만들었던 모든 보장은 사라진다.

SKILL.md를 Hermes로 옮기기 전에 확인할 네 가지 질문

SKILL.md~/.hermes/skills/로 복사하기 전에, 프런트매터와 본문을 네 가지 질문에 맞춰 점검하세요. 각각은 Claude Code 확장이 Hermes에서 어떤 방식으로 성능 저하되는지 정확히 대응합니다. 오류 없이 조용히 사라지기 때문에, 스킬을 배포하기 전에 다시 작성할지, 별도로 강제할지, 손실을 감수할지 판단할 수 있습니다.

  1. 프런트매터에 hook 블록이 선언되어 있나요? PreToolUse, PostToolUse, SessionStart, PermissionRequest, 또는 다른 lifecycle event가 있는지 확인하세요 . Hermes는 스킬의 active hook 블록을 읽지 않으므로 guard가 실행되지 않습니다. 해당 로직을 ctx.register_hook()을 쓰는 Hermes plugin hook으로 다시 작성하세요. 가장 가까운 대응 항목은 pre_tool_call입니다. 또는 블록을 제거하고, 더 이상 강제가 실행되지 않는다는 점을 스킬 본문에 기록해 누구도 여전히 동작한다고 오해하지 않게 하세요 .
  2. allowed-tools 또는 disallowed-tools를 사용하나요? 이 항목들은 Claude Code의 턴별 permission pool을 변경합니다. Hermes는 이를 완전히 무시하므로 전체 tool surface가 그대로 노출됩니다 . HOOK.yamlhandler.py를 사용하는 Hermes gateway hook으로 접근을 다시 강제하거나, 제한 없는 surface를 의식적으로 받아들이세요.
  3. 본문에 !`cmd` substitution이 있거나 CLAUDE_* 변수를 읽나요? 그렇다면 해당 marker는 model context에 그대로 들어가고, 변수는 빈 문자열로 해석됩니다. 명시적인 tool call이나 정적 reference text로 대체하세요.
  4. context: fork를 설정하거나 agent:에 subagent type을 지정하나요? Subagent isolation은 사라집니다. 스킬은 호출 세션과 같은 memory 및 tool surface를 가진 main conversation thread에서 실행됩니다 . 정말로 격리가 필요한 작업은 별도의 Hermes invocation으로 옮기세요.

반드시 붙잡고 있어야 할 차이는 vendor docs에 그대로 나와 있습니다:

"Claude Code skills follow the Agent Skills standard but extend it with invocation control, subagent execution, and dynamic context injection." — Claude Code documentation (source: code.claude.com)

어느 질문이든 "yes"라면 그 스킬은 harness에 묶여 있습니다. 형식은 Hermes에서 여전히 로드되지만, 동작은 함께 이동하지 않습니다.

동작하지 않는 항목을 Hermes pre_tool_call 등록으로 대체하기

Four questions to ask before transferring any SKILL.md to Hermes

점검 결과 harness-bound skill로 표시되면, 해결책은 복사가 아니라 Hermes 자체 hook API에 맞춘 재작성입니다. Hermes는 ctx.register_hook()을 통해 plugin hook을 노출하며, 문서화된 이름은 여덟 가지입니다. pre_tool_call, post_tool_call, pre_llm_call, post_llm_call, pre_verify, subagent_start, subagent_stop, 그리고 transform_tool_result입니다 . shell write를 막던 Claude Code PreToolUse matcher는 대략 deny response를 반환하는 pre_tool_call plugin hook에 대응됩니다. 의도는 겹치지만 ABI는 다릅니다. Event name도 일부만 맞습니다. 예를 들어 subagent_start는 직접 대응됩니다. 하지만 payload schema와 return contract가 다르므로, 파일이 아니라 로직을 port해야 합니다.

"Claude Code skills follow the Agent Skills standard but extend it with invocation control, subagent execution, and dynamic context injection." — Claude Code documentation (source: code.claude.com)

Plain package라면 이런 작업이 필요 없습니다. Claude-specific frontmatter 없이 instruction과 bundled scripts만 들어 있는 SKILL.md는 그대로 옮겨집니다. Hermes는 official, skills-sh, GitHub taps, ClawHub, direct URLs에서 설치할 수 있습니다 . 설치 시 Hermes는 자체 supply-chain 방어도 추가합니다. hub-installed skills는 data-exfiltration patterns, prompt injection, destructive commands를 대상으로 scan되며, dangerous 판정이 나오면 --force를 써도 설치가 차단됩니다 . 이는 defense-in-depth일 뿐, 잃어버린 hook enforcement를 대체하지는 않습니다.

핵심은 이렇습니다. agentskills.io spec에는 conformant runtime이 어떤 extension fields를 반드시 존중해야 하고 어떤 항목을 조용히 무시할 수 있는지 선언한 versioned compatibility matrix가 없습니다 . 그런 matrix가 생기기 전까지는 모든 migration을 manual triage로 다루세요. plain skills는 그대로 port하고, policy를 encode한 것은 Hermes pre_tool_call hook으로 다시 만드세요.

자주 묻는 질문

agentskills.io 규격을 지키면 SKILL.md가 Hermes와 Claude Code에서 똑같이 실행되나요?

아니요. 규격 준수는 기본 형식에만 적용됩니다. 필수 프런트매터인 name(최대 64자)과 description(최대 1024자), 번들로 포함되는 scripts/, references/, assets/ 디렉터리, 그리고 3단계 점진적 공개 구조가 그 범위입니다 . Claude Code의 자체 문서도 호출 제어, 서브에이전트 실행, 동적 컨텍스트 주입으로 표준을 확장한다고 밝히고 있습니다 . 이런 확장은 명세 위에 얹힌 기능이며, Hermes처럼 Claude가 아닌 호환 런타임에서는 조용히 아무 동작도 하지 않습니다.

Claude Code 훅 이벤트 중 Hermes에서 기능적으로 대응되는 것은 무엇인가요?

일부만 겹칩니다. Claude Code의 PreToolUsePostToolUsectx.register_hook()으로 등록하는 Hermes의 pre_tool_callpost_tool_call 플러그인 훅과 대략 대응되고, subagent_start는 이름이 그대로 일치합니다 . 하지만 Claude Code의 더 넓은 생명주기 이벤트인 SessionStart, UserPromptSubmit, PermissionRequest, PreCompact, WorktreeCreate 등은 직접 대응되는 항목이 없습니다 . 이벤트 페이로드와 반환 규약도 다르기 때문에, 훅 마이그레이션은 직접 이식이 아니라 다시 작성하는 작업에 가깝습니다.

Hermes에서는 allowed-tools / disallowed-tools 선언이 어떻게 처리되나요?

Hermes는 프런트매터를 읽지만 Claude Code의 턴별 권한 풀을 구현하지 않으므로, 해당 필드는 인식되지 않는 키로 취급됩니다. Bash(git *) 또는 Skill(name *) 같은 Claude 전용 문법에는 이식 가능한 대응 문법이 없습니다 . 실무적으로는 별도의 Hermes 게이트웨이 훅(HOOK.yaml + handler.py)이 명시적으로 제한하지 않는 한, 기본적으로 도구 접근이 제한되지 않는다는 뜻입니다 .

ClawHub에서 설치한 스킬에 활성 훅 블록이 들어 있을 수 있나요? 그리고 실행되나요?

허브를 통해 배포되는 SKILL.md에는 Claude Code 훅 프런트매터가 포함될 수 있지만, Hermes에서는 그 블록이 실행되지 않습니다. Hermes는 설치 시 보안 스캔을 수행해 데이터 유출, 프롬프트 인젝션, 파괴적 명령, 공급망 관련 신호를 탐지하며, dangerous 판정은 --force를 사용해도 차단합니다 . 그러나 실행 시점에는 훅 필드를 알 수 없는 키로 취급하므로 실제로 실행되지 않습니다. 따라서 설치 시 스캔은 심층 방어 수단일 뿐, 런타임 강제 적용을 대체하지는 못합니다.

런타임이 어떤 확장 필드를 지원해야 하는지 정리한 명세 수준의 호환성 매트릭스가 있나요?

없습니다. 2025년 12월 18일 공개 표준으로 발표된 agentskills.io 명세는 최소 실행 가능 형식을 정의하지만, 호환 런타임이 반드시 지원하거나 명시적으로 무시해야 하는 확장 필드를 버전별 매트릭스로 열거하지는 않습니다 . 런타임 계약은 파일시스템 접근, bash, 코드 실행 수준으로 축약됩니다 . 따라서 이식성은 버전 선언으로 보장되는 것이 아니라, 필드별로 직접 확인해야 합니다.

이 글이 도움이 되셨다면, 새 글이 올라올 때마다 이메일로 받아보세요.

구독하기