에러가 뜨면 나를 부르는 대신, 지라를 만들게 했다

Sentry → n8n → Jira → Slack 자동화 구축 회고
에러 하나가 뜨면 내가 하는 일은 늘 비슷했다.
Slack에서 Sentry 알림을 본다. 스택 트레이스를 읽는다. Jira를 열고 상위 이슈를 찾는다. 하위 버그를 만든다. 제목과 링크를 옮긴다. 다시 Slack에 “티켓 땄습니다”라고 알린다.
한 건에 3~5분. 문제는 이 시간 동안 내가 내리는 판단은 사실상 하나뿐이라는 점이었다.
이건 진짜 봐야 할 에러인가?
나머지는 전부 복사와 붙여넣기였다. 그래서 그 복사-붙여넣기만 떼어내 n8n에 넘기기로 했다.
그리고 싶었던 그림
처음부터 최종 형태는 크게 그렸다.

여기서 중요한 건 마지막 줄이다. 이 자동화의 목적은 사람을 빼는 것이 아니라, 사람이 판단해야 할 지점만 남기는 것이었다. 자동화가 어디서 멈춰야 하는지를 먼저 정해놓고 시작했다.
이번 글에서 다루는 범위는 Sentry 이벤트를 받아 Jira 하위 버그로 만들고 Slack에 알리는 데까지다. 로컬 Claude worker는 다음 단계로 남겨뒀다.
왜 n8n이었나
이번 목표는 새 서비스를 만드는 것이 아니었다. Webhook을 받고, 조건을 나누고, Jira와 Slack을 연결하는 일이었다.
코드로 별도 서버를 만들 수도 있었지만, 초기에 더 중요한 것은 각 단계에서 어떤 데이터가 들어오고 어디서 막히는지 눈으로 확인하는 것이었다. n8n은 노드마다 입력과 출력을 볼 수 있고, 실패한 실행도 남는다. 이 정도 자동화에는 그 투명성이 훨씬 유용했다.
1. “웹훅을 저장할 수가 없는데요”
첫 벽이었다. Sentry 프로젝트 설정에서 Webhook URL 입력창은 보이는데 Enable 체크박스도 Save 버튼도 눌리지 않았다.
원인은 단순했다. Webhooks Integration 자체를 프로젝트에 설치하지 않은 상태였다. 설정 화면이 보인다고 해서 기능이 활성화된 것은 아니었다. Settings → Integrations에서 Webhooks를 프로젝트에 먼저 설치해야 설정값이 실제로 저장된다.
이후에도 비슷한 함정을 여러 번 만났다. 여기서 얻은 습관은 이렇다.
저장이 안 된다
→ 권한 확인
→ Integration 설치 여부 확인
→ 입력값과 URL 유효성 확인
UI가 보인다는 것과 기능이 켜져 있다는 것은 별개다.
2. 저장은 됐는데 테스트가 안 되는 Webhook
n8n에 Webhook 노드를 만들고 URL을 등록했는데 아무것도 들어오지 않았다. 두 가지를 동시에 틀리고 있었다.
첫째, HTTP Method가 GET이었다. Sentry는 POST로 보낸다.
둘째, Workflow가 Active 상태가 아니었다. n8n은 테스트 URL과 운영 URL을 분리한다.
| 구분 | Test URL | Production URL |
|---|---|---|
| 경로 | /webhook-test/... |
/webhook/... |
| 동작 조건 | Listen for test event 상태 |
Workflow가 Active |
| 확인 위치 | 에디터 캔버스 | Executions 탭 |
https://n8n.example.com/webhook-test/sentry ← 테스트용
https://n8n.example.com/webhook/sentry ← 운영용
운영 URL은 Workflow가 비활성화되어 있으면 아래처럼 응답한다.
404 The requested webhook "POST sentry" is not registered.
문구만 보면 URL이 틀린 것 같지만, 실제로는 URL은 맞고 Workflow 스위치가 꺼져 있었던 것이다.

간단한 전달 경로 테스트는 curl로 할 수 있다.
curl -X POST 'https://n8n.example.com/webhook-test/sentry' \
-H 'Content-Type: application/json' \
-d '{"test":true,"message":"hello"}'
다만 이건 n8n이 POST를 받는지 확인하는 테스트다. 실제 Sentry payload 구조를 확인하려면 Sentry의 Send Test Event도 한 번 보내야 한다. 둘은 목적이 다르다.
3. 필터가 사실은 이 자동화의 본체였다
Webhook과 Jira를 바로 연결하면 동작은 한다. 하지만 그대로 운영에 붙이면 같은 이슈가 반복될 때 Jira가 티켓으로 넘쳐난다.
Sentry의 이벤트 전송은 “새로운 버그 티켓 하나”와 일대일 대응이 아니다. 같은 문제가 여러 번 발생하면 이벤트도 여러 번 온다. 그래서 자동화의 핵심은 연결 자체보다 무엇을 통과시키지 않을지 결정하는 것이었다.
현재 기본 필터는 다음과 같다.
이벤트 수신
↓
대상 프로젝트인가?
├─ 아니오 → 무시
└─ 예
↓
production 환경인가?
├─ 아니오 → 무시
└─ 예
↓
error / fatal인가?
├─ 아니오 → 무시
└─ 예
↓
healthcheck 계열인가?
├─ 예 → 무시
└─ 아니오 → Jira 생성 후보

n8n에서는 조건을 하나의 거대한 표현식으로 작성할 수도 있다.
{{
['error', 'fatal'].includes(
($json.body?.data?.event?.level ?? '').toLowerCase()
)
&&
!String($json.body?.data?.event?.message ?? '').toLowerCase()
.includes('healthcheck')
&&
String($json.body?.data?.project?.slug ?? '').toLowerCase()
.includes('sportlog')
}}
하지만 운영용으로는 프로젝트 → 환경 → 레벨 → 신규 여부 → 중복 여부처럼 IF를 쪼개는 편이 낫다. 어느 조건에서 걸러졌는지 실행 이력만 보고 알 수 있기 때문이다.
여기서 특히 중요한 점이 하나 있다. 필드 경로는 문서만 보고 확정하면 안 된다. 실제 Webhook 실행 데이터를 보고 body.data.event.level인지, body.level인지 확인해야 한다. 틀린 표현식은 에러 대신 조용한 false를 만든다. 조용한 false가 제일 무섭다.
아직 남은 필터: 신규 이슈와 중복 방지
현재 필터만으로는 같은 Sentry issue가 재발생했을 때 Jira 하위 이슈가 중복 생성될 수 있다. 운영 전에는 아래 단계를 추가할 예정이다.
Sentry issue ID 추출
↓
Jira에서 같은 ID 검색
↓
있음 → 새 이슈 생성하지 않음, 필요하면 Slack 재발생 알림만
없음 → Jira 하위 버그 생성
Sentry issue ID는 Jira label 또는 전용 Custom Field에 저장한다.
project = SLOG AND labels = "sentry-123456789"
그리고 “신규 이슈” 판정은 Sentry Alert Rule의 first seen 조건 또는 issue lifecycle 이벤트처럼, 의미가 보장된 신호를 사용하는 편이 안전하다.
4. Jira의 “하위에 넣기”는 생각보다 규칙이 빡빡하다
특정 이슈 아래에 버그를 매달고 싶었다. 하지만 Jira 기본 구조에서 일반 Bug 타입은 다른 이슈의 하위로 들어갈 수 없다. 하위로 넣으려면 Sub-task 계열 타입이어야 한다.
선택지는 둘이었다.
Sub-task또는버그 (하위)로 생성: 부모 아래 트리로 쌓인다.- 일반
Bug로 생성 후 링크:relates to,is caused by등으로 연결한다.
에러 티켓은 상위 트래킹 이슈 아래에 모아보는 게 목적이었다. 그래서 SLOG-595 아래에 버그 (하위)를 생성하도록 잡았다.
SLOG-595 [상위 트래킹 이슈]
├─ SLOG-596 [버그 (하위)] TypeError: ...
├─ SLOG-597 [버그 (하위)] API 500
└─ SLOG-598 [버그 (하위)] Null reference

트리 구조가 곧 대시보드가 된다.
5. 자동화의 절반은 인증 유지보수다
Jira 노드를 붙이자마자 401 Authorization failed가 떴다. 프로젝트 목록과 이슈 타입 목록을 불러오지 못했다. 다른 계정 Credential로 바꾸니 그제야 드롭다운이 채워졌다.
Slack 노드를 붙이자 이번에는 다음 오류가 나왔다.
Slack error response: "token_revoked"
예전에 다른 용도로 만들어둔 OAuth Credential을 재사용하려다 막힌 것이다.
이 두 번을 겪고 생각이 바뀌었다. 자동화 Workflow의 진짜 취약점은 로직이 아니라 토큰이다. 로직은 한 번 맞으면 비교적 오래 간다. 토큰은 만료되고, 폐기되고, 권한이 바뀌고, 때로는 조용히 죽는다.
그래서 실패 알림도 설계에 넣는다.
❌ Sentry 자동 처리 실패
단계: Jira 이슈 생성
원인: 401 Unauthorized
자동화를 만들 때 실패 알림은 옵션이 아니라 필수 노드다.
6. Slack 채널 설계는 곧 권한 설계다
Slack 설정에서 잠깐 멈칫했다. 내 계정 Credential을 넣으면 내가 나에게 메시지를 보내는 그림이 되는 건가 싶었다.
정리는 간단하다.
Credential = 누가 보내는가
Send Message To = 누가 받는가
발신은 전용 Slack Bot인 PatchPilot으로 잡고, 수신은 개인 DM 대신 기존 Sentry 채널로 잡았다. 이렇게 하면 특정 사람이 자리를 비워도 파이프라인은 유지되고, 프로젝트 구성원도 함께 상황을 볼 수 있다.
PatchPilot Bot
│ 발신
▼
#sentry 채널
│ 수신
▼
프로젝트 구성원

Slack 메시지에는 Jira 키와 바로가기 링크를 넣는다.
🚨 Sentry 오류가 Jira에 등록됐습니다.
Jira: SLOG-596
링크: https://hamalab.atlassian.net/browse/SLOG-596
@PatchPilot SLOG-596 분석하고 수정해서 테스트 후 PR 생성해줘
다음 단계의 권한 모델
기존 Sentry 채널을 Claude 작업 지시 채널로도 쓸 수 있다. 대신 채널 구성원이 누구나 실행 가능한 작업과 승인자만 가능한 작업을 구분해야 한다.
| 명령 | 허용 범위 |
|---|---|
| 분석 / 수정 / 테스트 / PR 생성 | 프로젝트 채널 구성원 |
| CBT 배포 | 승인자 |
| 실서버 배포 | 자동 실행 금지, 사람 수동 실행 |
채널 하나를 고르는 일은 협업 편의의 문제가 아니라 “누가 이 자동화를 실행할 수 있는가”를 정하는 권한 모델의 문제였다.
7. Sentry, Jira, Slack 사이의 데이터 매핑
Webhook을 받으면 무엇을 어디에 넣을지 미리 정리해두면 노드 설정이 단순해진다.
| Sentry 데이터 | Jira | Slack |
|---|---|---|
| 프로젝트 / slug | 프로젝트 필터, Label | 채널 분기 기준 |
| 오류 메시지 | Summary | 알림 제목 |
| 환경 / level | Description | 본문 |
| Sentry issue ID | 중복 방지 Label / Custom Field | 필요 시 표시 |
| Sentry issue URL | Description | 바로가기 링크 |
주의할 점도 있다. 스택 트레이스, 사용자 정보, 요청 본문에는 민감한 값이 섞일 수 있다. 원본 JSON 전체를 Jira나 Slack으로 넘기기보다 사용자 식별자, 토큰성 문자열, 민감한 request data는 마스킹하는 편이 안전하다.
또한 장애 상황에서는 이벤트가 한꺼번에 몰린다. 중복 방지와 함께 같은 Sentry issue는 일정 시간에 한 번만 알리도록 rate limit을 두는 것도 운영 전에 챙길 항목이다.
지금까지의 결과
[완료한 기반]
Sentry 이벤트
→ n8n Webhook (POST)
→ 프로젝트 / production / error·fatal / healthcheck 제외 필터
→ Jira 하위 버그 생성
→ Slack Bot 알림 노드
[운영 전 보강]
→ Sentry issue ID 기반 중복 방지
→ Jira / Slack 실패 알림
→ Slack 채널 수신 테스트
→ 민감 데이터 마스킹과 알림 폭주 제어
[다음 단계]
→ Slack Socket Mode + Windows 로컬 worker
→ Claude Code 실행
→ 코드 수정 → 테스트 → PR 생성
→ 결과 Slack 보고
→ 배포는 사람이
남은 세 문장
하나. 자동화는 완성하는 게 아니라 한 칸씩 늘리는 것이다.
처음부터 Sentry → Jira → Slack → 로컬 worker → PR → 배포까지 다 그렸지만, 실제로는 Webhook 하나가 뜨는 것부터 확인했다. 한 단계가 확실히 도는 걸 본 다음에 다음 노드를 붙였다. 이 순서를 안 지켰으면 어디서 깨졌는지 영원히 몰랐을 것이다.
둘. 필터가 파이프라인의 본체다.
연결은 짧은 시간 안에 끝난다. 나머지 시간은 전부 무엇을 통과시키지 않을 것인가에 쓴다. 자동화의 가치는 무언가를 실행하는 데서 나오는 게 아니라, 실행하지 않아도 될 것을 걸러내는 데서 나온다.
셋. 사람이 멈추는 지점을 먼저 정해라.
수정도 하고, 테스트도 하고, PR도 만들 수 있다. 하지만 배포 버튼은 사람이 누른다. 이 선을 나중에 그으려고 하면 자동화의 범위는 계속 밀린다. 처음에 그어두면 그 선이 설계의 기준이 된다.