본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 04. 22:07

polyhook에 Gemini CLI 추가하기: 그 과정에서 겪은 일들

요약

다양한 AI 코딩 도구의 훅(hook)을 정규화하는 라이브러리인 polyhook에 Gemini CLI 지원을 추가하는 과정을 다룹니다. Gemini CLI의 라이프사이클 이벤트와 프로토콜을 분석하고, Claude Code와의 필드 이름 충돌 문제를 해결하는 기술적 방법을 설명합니다.

핵심 포인트

  • Gemini CLI의 11개 라이프사이클 이벤트와 프로토콜 분석
  • Claude Code와 Gemini CLI 간의 JSON 필드 이름 충돌 해결 전략
  • 환경 변수를 활용한 도구 감지 및 JSON 휴리스틱 폴백 구현
  • Gemini CLI 이벤트를 polyhook 표준 형식으로 매핑 및 직렬화

저는 한동안 polyhook 작업을 해왔습니다. 짧게 요약하자면, polyhook는 하나의 훅 (hook) 바이너리를 작성하면 Claude Code, Cursor, Windsurf, Cline, 그리고 Amp에서 모두 작동할 수 있게 해주는 라이브러리입니다. 이 도구들은 각각 훅을 가지고 있으며, 각 도구는 여러분의 훅 스크립트로 완전히 다른 JSON을 전송합니다. polyhook은 그 중간에 위치하여 이를 정규화 (normalize) 합니다.

누군가 Gemini CLI에도 훅이 있는데 polyhook이 이를 지원하지 않는다고 지적했습니다. 일리 있는 말이었습니다. 그래서 저는 이를 추가하기 위해 자리에 앉았습니다.

단순히 형식을 추측하고 싶지 않았기에, 코드를 건드리기 전에 실제로 Gemini CLI 문서를 읽어보았습니다. 그들의 훅 시스템은 상당히 탄탄했습니다. 세션 시작/종료부터 개별 도구 호출 (tool calls), 그리고 모델 레이어 (model layer) 자체에 이르기까지 모든 것을 다루는 11개의 라이프사이클 이벤트 (lifecycle events)가 있었습니다. 심지어 모델이 어떤 도구를 사용할지 선택하기 전에 발생하는 BeforeToolSelection 이벤트도 있었습니다. polyhook이 지원하는 다른 도구 중에는 이런 것이 없습니다.

프로토콜은 깔끔합니다. 훅 스크립트는 stdin으로부터 JSON을 읽고, stdout으로 JSON을 쓰며, stderr는 로그 전용으로 사용됩니다. Exit 0은 stdout의 응답이 정답임을 의미합니다. Exit 2는 일어나려던 모든 것을 차단 (block)함을 의미합니다. 그게 전부입니다.

제가 예상하지 못했던 것은 필드 이름 충돌 (field name collision)이었습니다.

Gemini CLI와 Claude Code 모두 페이로드 (payload)에 hook_event_name을 보냅니다. 둘 다 tool_name을 보냅니다. 둘 다 tool_input을 보냅니다. 이벤트 이름의 값 (value) 없이 각각의 페이로드를 개별적으로 본다면

해결책은 필드 조합을 확인하기 전에 hook_event_name의 *값 (value)*을 먼저 확인하는 것이었습니다. 만약 그 값이 Gemini CLI의 이벤트 이름 중 하나라면, 이를 Gemini CLI로 레이블링하고 거기서 멈춥니다. Claude Code의 필드 이름 휴리스틱 (heuristic)은 이 첫 번째 확인이 발생하지 않을 때만 실행됩니다. 여기서 순서가 매우 중요합니다. 순서를 뒤집으면 Gemini CLI 지원이 소리 없이 깨지게 되는데, 이는 최악의 종류의 버그입니다.

실제로 환경 변수 (env var) 감지가 어쨌든 대부분의 케이스를 처리합니다. Claude Code가 항상 CLAUDE_CODE_VERSION을 설정하는 것과 마찬가지로, Gemini CLI는 훅 (hook)을 호출할 때 항상 GEMINI_PROJECT_DIR을 설정합니다. 따라서 JSON 휴리스틱 (heuristic)은 실제 도구 외부에서 누군가가 자신의 훅을 테스트할 때를 위한 일종의 폴백 (fallback)일 뿐입니다.

감지 문제가 해결된 후, 나머지는 상당히 기계적인 작업이었습니다. Gemini CLI의 이벤트 이름을 polyhook의 표준 이름으로 매핑합니다 (BeforeTooltool:before, SessionStartsession:start 등). Gemini CLI의 도구 이름도 매핑합니다 (run_shell_commandbash, replaceedit_file, google_web_searchweb_search). 그리고 Gemini CLI가 기대하는 형식인 승인 시 {"decision": "allow"}, 차단 시 {"decision": "deny", "reason": "..."}를 출력하는 응답 직렬화기 (response serializer)를 추가합니다.

이 모든 과정에서 6개의 소스 파일이 수정되었습니다. polyhook의 코어는 WASM으로 컴파일되며 모든 언어 SDK (Rust, TypeScript, Go, Python, .NET)는 해당 바이너리에 대한 얇은 래퍼 (thin wrapper)일 뿐이기 때문에, 스키마 열거형 (schema enum)에 `

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0