Webhooks를 사용한 자동화된 GitHub PR 댓글
anchor alias
anchor alias
Webhooks를 사용한 자동화된 GitHub PR 댓글
이 가이드는 Hermes Agent를 GitHub에 연결하여 끌어오기 요청의 차이점을 자동으로 가져오고, 코드 변경 사항을 분석하고, 수동 메시지 없이 웹후크 이벤트에 의해 트리거되는 댓글을 게시하는 과정을 안내합니다.
PR이 열리거나 업데이트되면 GitHub는 Hermes 인스턴스에 웹훅 POST를 보냅니다. Hermes는 gh CLI를 통해 diff를 검색하도록 지시하는 프롬프트로 에이전트를 실행하고 응답은 PR 스레드에 다시 게시됩니다.
공개 URL이 없거나 빠르게 시작하고 싶다면 GitHub PR 검토 에이전트 구축을 확인하세요. 크론 작업을 사용하여 일정에 따라 PR을 폴링하고 NAT 및 방화벽 뒤에서 작동합니다.
전체 웹훅 플랫폼 참조(모든 구성 옵션, 전송 유형, 동적 구독, 보안 모델)를 보려면 웹훅을 참조하세요.
웹훅 페이로드에는 공격자가 제어하는 데이터가 포함되어 있습니다. PR 제목, 커밋 메시지 및 설명에는 악의적인 지침이 포함될 수 있습니다. 웹훅 엔드포인트가 인터넷에 노출되면 샌드박스 환경(Docker, SSH 백엔드)에서 게이트웨이를 실행하세요. 아래의 보안 섹션을 참조하세요.
전제 조건
- Hermes 에이전트가 설치되어 실행 중입니다(
hermes gateway). ghCLI가 게이트웨이 호스트(gh auth login)에 설치 및 인증되었습니다.- Hermes 인스턴스에 대해 공개적으로 연결 가능한 URL(로컬에서 실행하는 경우 ngrok를 사용한 로컬 테스트 참조)
- GitHub 저장소에 대한 관리자 액세스(웹훅 관리에 필요)
1단계 - 웹훅 플랫폼 활성화
~/.hermes/config.yaml에 다음을 추가하세요.
platforms:
webhook:
enabled: true
extra:
port: 8644 # default; change if another service occupies this port
rate_limit: 30 # max requests per minute per route (not a global cap)
routes:
github-pr-review:
secret: "your-webhook-secret-here" # must match the GitHub webhook secret exactly
events:
- pull_request
# The agent is instructed to fetch the actual diff before reviewing.
# {number} and {repository.full_name} are resolved from the GitHub payload.
prompt: |
A pull request event was received (action: {action}).
PR #{number}: {pull_request.title}
Author: {pull_request.user.login}
Branch: {pull_request.head.ref} → {pull_request.base.ref}
Description: {pull_request.body}
URL: {pull_request.html_url}
If the action is "closed" or "labeled", stop here and do not post a comment.
Otherwise:
1. Run: gh pr diff {number} --repo {repository.full_name}
2. Review the code changes for correctness, security issues, and clarity.
3. Write a concise, actionable review comment and post it.
deliver: github_comment
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{number}"
주요 필드:
| 필드 | 설명 |
|---|---|
secret(경로 수준) | 이 경로의 HMAC 비밀번호입니다. 생략되면 extra.secret 전역으로 대체됩니다. |
events | 허용할 X-GitHub-Event 헤더 값 목록입니다. 빈 목록 = 모두 수락합니다. |
prompt | 템플릿; {field} 및 {nested.field}은 GitHub 페이로드에서 확인됩니다. |
deliver | github_comment 게시물은 gh pr comment을 통해 게시됩니다. log는 게이트웨이 로그에 기록합니다. |
deliver_extra.repo | 예를 들어 다음과 같이 해결됩니다. org/repo 페이로드에서. |
deliver_extra.pr_number | 페이로드의 PR 번호로 확인됩니다. |
GitHub 웹훅 페이로드에는 PR 메타데이터(제목, 설명, 지점 이름, URL)가 포함되지만 차이점은 포함되지 않습니다. 위의 프롬프트는 에이전트에게 gh pr diff을 실행하여 실제 변경 사항을 가져오도록 지시합니다. terminal 도구는 기본 hermes-webhook 도구 세트에 포함되어 있으므로 추가 구성이 필요하지 않습니다.
2단계 - 게이트웨이 시작
hermes gateway
다음을 확인해야 합니다.
[webhook] Listening on 0.0.0.0:8644 — routes: github-pr-review
실행 중인지 확인합니다.
curl http://localhost:8644/health
# {"status": "ok", "platform": "webhook"}
3단계 - GitHub에 웹훅 등록
- 저장소로 이동 → 설정→웹훅→웹훅 추가
- 작성:
- 페이로드 URL:
https://your-public-url.example.com/webhooks/github-pr-review - 콘텐츠 유형:
application/json - 비밀: 경로 구성에서
secret에 대해 설정한 것과 동일한 값 - 어떤 이벤트인가요?→ 개별 이벤트 선택 →풀 요청 확인
- 페이로드 URL:
- 웹훅 추가를 클릭하세요.
GitHub는 연결을 확인하기 위해 즉시 ping 이벤트를 보냅니다. 이는 안전하게 무시됩니다. ping은 events 목록에 없으며 {"status": "ignored", "event": "ping"}을 반환합니다. DEBUG 수준에서만 기록되므로 기본 로그 수준에서는 콘솔에 표시되지 않습니다.
4단계 - 테스트 PR 열기
브랜치를 생성하고, 변경 사항을 푸시하고, PR을 엽니다. 30~90초 이내에(PR 크기 및 모델에 따라 다름) Hermes는 리뷰 댓글을 게시해야 합니다.
에이전트의 진행 상황을 실시간으로 확인하려면 다음 안내를 따르세요.
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"
ngrok를 사용한 로컬 테스트
Hermes가 랩톱에서 실행 중인 경우 ngrok을 사용하여 노출합니다.
ngrok http 8644
``https://...ngrok-free.app` URL을 복사하여 GitHub 페이로드 URL로 사용하세요. 무료 ngrok 계층에서는 ngrok가 다시 시작될 때마다 URL이 변경됩니다. 각 세션마다 GitHub 웹후크를 업데이트하세요. 유료 ngrok 계정은 고정 도메인을 갖습니다.
`curl`을 사용하여 직접 정적 경로를 스모크 테스트할 수 있습니다. GitHub 계정이나 실제 PR이 필요하지 않습니다.
:::tip[Use `deliver: log` when testing locally]
테스트하는 동안 구성에서 `deliver: github_comment`을 `deliver: log`로 변경하세요. 그렇지 않으면 에이전트는 테스트 페이로드의 가짜 `org/repo#99` 저장소에 댓글을 게시하려고 시도하지만 실패합니다. 프롬프트 출력에 만족하면 `deliver: github_comment`으로 다시 전환하세요.
:::
```bash
SECRET="your-webhook-secret-here"
BODY='{"action":"opened","number":99,"pull_request":{"title":"Test PR","body":"Adds a feature.","user":{"login":"testuser"},"head":{"ref":"feat/x"},"base":{"ref":"main"},"html_url":"https://github.com/org/repo/pull/99"},"repository":{"full_name":"org/repo"}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print "sha256="$2}')
curl -s -X POST http://localhost:8644/webhooks/github-pr-review \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: pull_request" \
-H "X-Hub-Signature-256: $SIG" \
-d "$BODY"
# Expected: {"status":"accepted","route":"github-pr-review","event":"pull_request","delivery_id":"..."}
그런 다음 에이전트 실행을 지켜보세요.
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"
hermes webhook test <name>은 hermes webhook subscribe으로 생성된 동적 구독에만 작동합니다. config.yaml의 경로는 읽지 않습니다.
특정 작업으로 필터링
GitHub는 opened, synchronize, reopened, closed, labeled 등 다양한 작업에 대해 pull_request 이벤트를 보냅니다. events 목록은 X-GitHub-Event 헤더 값으로만 필터링합니다. 라우팅 수준에서는 작업 하위 유형으로 필터링할 수 없습니다.
1단계의 프롬프트는 closed 및 labeled 이벤트에 대해 조기 중지하도록 에이전트에 지시하여 이미 이를 처리하고 있습니다.
"여기서 중지" 명령은 의미 있는 검토를 방해하지만 에이전트는 작업에 관계없이 모든 pull_request 이벤트에 대해 완료될 때까지 계속 실행됩니다. GitHub 웹후크는 이벤트 유형(pull_request, push, issues 등)으로만 필터링할 수 있습니다. 작업 하위 유형(opened, closed, labeled). 하위 작업에는 라우팅 수준 필터가 없습니다. 대용량 저장소의 경우 이 비용을 수락하거나 웹훅 URL을 조건부로 호출하는 GitHub Actions 워크플로를 사용하여 업스트림을 필터링하세요.
Jinja2 또는 조건부 템플릿 구문이 없습니다.
{field}및{nested.field}은 지원되는 유일한 대체 항목입니다. 다른 모든 내용은 그대로 에이전트에 전달됩니다.
일관된 검토 스타일을 위한 기술 사용
상담원에게 일관된 리뷰 페르소나를 제공하려면 Hermes 스킬을 로드하세요. config.yaml의 platforms.webhook.extra.routes 내부 경로에 skills을 추가합니다.
platforms:
webhook:
enabled: true
extra:
routes:
github-pr-review:
secret: "your-webhook-secret-here"
events: [pull_request]
prompt: |
A pull request event was received (action: {action}).
PR #{number}: {pull_request.title} by {pull_request.user.login}
URL: {pull_request.html_url}
If the action is "closed" or "labeled", stop here and do not post a comment.
Otherwise:
1. Run: gh pr diff {number} --repo {repository.full_name}
2. Review the diff using your review guidelines.
3. Write a concise, actionable review comment and post it.
skills:
- review
deliver: github_comment
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{number}"
참고: 목록에서 발견된 첫 번째 스킬만 로드됩니다. 헤르메스는 여러 기술을 쌓지 않습니다. 후속 항목은 무시됩니다.
대신 Slack 또는 Discord로 응답 보내기
경로 내의 deliver 및 deliver_extra 필드를 대상 플랫폼으로 바꿉니다.
# Inside platforms.webhook.extra.routes.<route-name>:
# Slack
deliver: slack
deliver_extra:
chat_id: "C0123456789" # Slack channel ID (omit to use the configured home channel)
# Discord
deliver: discord
deliver_extra:
chat_id: "987654321012345678" # Discord channel ID (omit to use home channel)
대상 플랫폼도 게이트웨이에서 활성화되고 연결되어야 합니다. chat_id을 생략하면 해당 플랫폼에 구성된 홈 채널로 응답이 전송됩니다.
유효한 deliver 값: log · github_comment · telegram · discord · slack · signal · sms
GitLab 지원
동일한 어댑터가 GitLab에서도 작동합니다. GitLab은 인증을 위해 X-Gitlab-Token을 사용합니다(HMAC가 아닌 일반 문자열 일치). Hermes는 두 가지를 자동으로 처리합니다.
이벤트 필터링의 경우 GitLab은 X-GitLab-Event을 Merge Request Hook, Push Hook, Pipeline Hook과 같은 값으로 설정합니다. events의 정확한 헤더 값을 사용하세요.
events:
- Merge Request Hook
GitLab 페이로드 필드는 GitHub와 다릅니다. MR 제목은 {object_attributes.title}, MR 번호는 {object_attributes.iid}입니다. 전체 페이로드 구조를 발견하는 가장 쉬운 방법은 Webhook 설정에 있는 GitLab의 Test 버튼과 Recent Deliveries 로그를 결합하는 것입니다. 또는 경로 구성에서 prompt을 생략합니다. 그러면 Hermes가 형식화된 JSON으로 전체 페이로드를 에이전트에 직접 전달하고 에이전트의 응답(deliver: log으로 게이트웨이 로그에 표시됨)이 해당 구조를 설명합니다.
보안 참고 사항
- **프로덕션에서는 절대로
INSECURE_NO_AUTH**을 사용하지 마세요. 서명 확인이 완전히 비활성화됩니다. 오로지 지역 발전을 위한 것입니다. - 웹훅 비밀을 주기적으로 순환하고 GitHub(웹훅 설정)와
config.yaml에서 업데이트하세요. - 속도 제한은 기본적으로 경로당 30req/min입니다(
extra.rate_limit을 통해 구성 가능). 이를 초과하면429이 반환됩니다. - 중복 전송(웹훅 재시도)은 1시간 멱등성 캐시를 통해 중복 제거됩니다. 캐시 키는
X-GitHub-Delivery(있는 경우),X-Request-ID, 밀리초 타임스탬프입니다. 전송 ID 헤더가 모두 설정되지 않은 경우 재시도는 중복이 제거되지 않습니다. - 즉시 삽입: PR 제목, 설명, 커밋 메시지는 공격자가 제어합니다. 악의적인 PR은 에이전트의 작업을 조작하려고 시도할 수 있습니다. 공용 인터넷에 노출되면 샌드박스 환경(Docker, VM)에서 게이트웨이를 실행합니다.
문제 해결
| 증상 | 확인 |
|---|---|
401 Invalid signature | config.yaml의 비밀이 GitHub 웹훅 비밀과 일치하지 않습니다. |
404 Unknown route | URL의 경로 이름이 routes:의 키와 일치하지 않습니다. |
429 Rate limit exceeded | 경로당 30 요청/분 초과 - GitHub의 UI에서 테스트 이벤트를 다시 전달할 때 일반적입니다. 잠시 기다리거나 extra.rate_limit을(를) 모으세요 |
| 댓글이 게시되지 않았습니다. | gh이 설치되지 않았거나, PATH에 없거나, 인증되지 않았습니다(gh auth login). |
| 에이전트가 실행되지만 댓글이 없습니다. | 게이트웨이 로그를 확인하세요. 에이전트 출력이 비어 있거나 "SKIP"인 경우에도 전달이 시도됩니다. |
| 이미 사용 중인 포트 | config.yaml에서 extra.port을 변경합니다. |
| 에이전트가 실행되지만 PR 설명만 검토합니다. | 프롬프트에 gh pr diff 명령이 포함되어 있지 않습니다. 차이점이 웹훅 페이로드에 없습니다. |
| 핑 이벤트를 볼 수 없습니다 | 무시된 이벤트는 DEBUG 로그 수준에서만 {"status":"ignored","event":"ping"}을 반환합니다. GitHub의 전달 로그를 확인하세요(repo → 설정 → 웹훅 → 웹훅 → 최근 전달). |
GitHub의 최근 전달 탭(repo → 설정 → 웹후크 → 웹후크)에는 모든 전달에 대한 정확한 요청 헤더, 페이로드, HTTP 상태 및 응답 본문이 표시됩니다. 서버 로그를 건드리지 않고 오류를 진단하는 가장 빠른 방법입니다.
전체 구성 참조
platforms:
webhook:
enabled: true
extra:
host: "0.0.0.0" # bind address (default: 0.0.0.0)
port: 8644 # listen port (default: 8644)
secret: "" # optional global fallback secret
rate_limit: 30 # requests per minute per route
max_body_bytes: 1048576 # payload size limit in bytes (default: 1 MB)
routes:
<route-name>:
secret: "required-per-route"
events: # = accept all; otherwise list X-GitHub-Event values
prompt: "" # {field} / {nested.field} resolved from payload
skills: # first matching skill is loaded (only one)
deliver: "log" # log | github_comment | telegram | discord | slack | signal | sms
deliver_extra: {} # repo + pr_number for github_comment; chat_id for others
다음은 무엇입니까?
- Cron 기반 PR 리뷰 — 일정에 따라 PR 폴링, 공개 엔드포인트 필요 없음
- 웹훅 참조 — 웹훅 플랫폼에 대한 전체 구성 참조
- 플러그인 빌드 — 리뷰 로직을 공유 가능한 플러그인으로 패키지화
- 프로필 — 자체 메모리와 구성을 사용하여 전용 리뷰어 프로필을 실행합니다.