[HAN-268] Jarvis 자체 [APPROVED] 응답으로도 execution 트리거 — Raphael 발송 Spec Gate 핸들러 확장
Summary
HAN-268과 HAN-168을 묶음 검토했으나 분리 진행으로 결정. 사용자 가설의 공통 패턴("비-멤버 봇 멘션 무효")이 두 이슈에 동일하게 적용되지 않음 — 실제 실패 메커니즘이 다름. HAN-268은 본 작업으로 진행, HAN-168은 별도 일정 제안.
Context
위임 컨텍스트
Jarvis가 plan 단계에서 두 이슈를 묶음 검토 요청:
- **공통 가설**: "멤버 아닌 봇에 멘션·메시지 보내봤자 트리거 안 됨"의 변종
- **공통 해결 후보**: 본문 패턴 + 페르소나 라우팅 테이블 (단일 인입), 또는 페르소나 직접 DM (HAN-167 옵션 B 계열)
HAN-268 (본 작업)
spec_gate.py 자동 발송 [Spec Gate] 브리핑은 _handle_approval_briefing가 정상 트리거 → [APPROVED] → 👍 + approved 라벨 → execution 픽업 ✅
문제: Raphael 페르소나가 (Asurada 위임 후 follow-up 패턴으로) 직접 발송한 [Spec Gate] 브리핑은 Jarvis [APPROVED] 응답에도 라벨 부여·execution 트리거 안 됨.
HAN-168 (별도 이슈)
log_monitor.py의 에러 알림이 SLACK_PROACTIVE_CHANNEL 미설정 시 사용자 DM으로 fallback 되는데, Asurada가 해당 DM 멤버가 아니라 본문에 <@U0B3QQCGZ9C> 멘션 텍스트가 있어도 app_mention 이벤트 안 발생.
Scope
묶음 검토 결과: **분리 진행**
| 비교 축 | HAN-268 | HAN-168 |
|---|---|---|
| 문제 도메인 | Approval 파이프라인 state machine (Spec Gate handler) | 알림 라우팅 destination (Asurada delivery) |
| 코드 surface | `bridge.py:_handle_approval_briefing` + `on_message` 분기 + `spec_gate_pending.json` 하이드레이션 | `log_monitor.py:_send_slack_dm` |
| 트리거 메커니즘 | `message` 이벤트 (Jarvis는 #agent-approvals 멤버) | `app_mention` 이벤트 (Asurada는 Jarvis-user DM 비멤버) |
| 실패 본질 | 핸들러 dispatch / state / re-entry 로직 | 봇 멤버십 (구조적 한계) |
| 해결 형태 | 핸들러 idempotency + 발송자 무관 인식 + 라벨 save 보장 | Asurada 직접 delivery 경로 추가 |
사용자 공통 가설의 한계
> "멤버 아닌 봇에 멘션·메시지 보내봤자 트리거 안 됨"
- **HAN-268**: Jarvis는 #agent-approvals **이미 멤버**.
bridge.py:1910에bot_id guard intentionally omitted주석으로 Raphael bot 메시지도 받음. 실패 원인이 멤버십이 아닌 핸들러 로직. - **HAN-168**: Asurada는 Jarvis-user DM **비멤버** (Slack IM 채널은 개설 봇 + 사용자 2인만 멤버 —
reference_slack_api_patterns.md참조). 진짜 멤버십 문제.
공통 해결 후보 A (본문 패턴 + 라우팅 테이블) 검토
- **HAN-268에 적합**:
_SPEC_GATE_PATTERN이미 본문 패턴 기반. 발송자 무관 인식은 거의 구현됨. - **HAN-168에 부적합**: DM 채널은 Asurada가 비멤버이므로 본문 패턴이 있어도 Asurada 봇이 이벤트를 받지 못함. 라우팅 테이블만으로는 해결 불가 — 별도 chat_postMessage 경로 필요.
공통 해결 후보 B (페르소나 직접 DM)
- **HAN-168에 적합**: log_monitor가 Asurada-user DM으로 직접 chat_postMessage (또는 다중봇 채널 게시).
- **HAN-268에 부적합**: 이미 채널 게시 모델. DM 경로 추가는 본 문제 해결과 무관.
결론
두 이슈는 수면 위 증상은 유사하나 수면 아래 메커니즘이 다름. 묶음 해결 시 premature abstraction 위험. 각자 focused fix로 진행하고, 만일 3번째 유사 이슈가 등장하면 그 시점에 추상화 검토.
- **HAN-268**: 본 plan 기반 진행
- **HAN-168**: 별도 진행 일정 제안 (priority Low → 본 작업 머지 후, 5월 말~6월 초 한가할 때)
Plan / Scope (HAN-268)
사용자 결정 (2026-05-22 이슈 본문 추가)
> "전체 자동화. 밀려오는 작업을 다 파악하기 힘들고, 문제 생기면 그때 수정하는 전략."
→ Jarvis [APPROVED] 단독으로 execution 트리거. priority High로 격상. 단 회수 메커니즘 필수 (fail-safe).
작업 항목
- [ ] **재현 검증** — Raphael persona가 #agent-approvals에 [Spec Gate] 브리핑 발송 시
_handle_approval_briefing실제 호출 여부 로그 확인
- 확인 포인트: event.subtype 값, _SPEC_GATE_PATTERN 매칭 여부, 이후 reactions_add / save_issue 결과
- [ ] **근본 원인 식별** — 다음 가설 중 어느 것이 실제 실패인지 결정
- 가설 A: event.subtype="bot_message"로 인해 not event.get("subtype") 가드에서 차단
- 가설 B: _handle_approval_briefing 호출되었으나 reactions_add / save_issue silent fail (log.warning만 남음)
- 가설 C: Jarvis의 [APPROVED] 응답이 다른 경로(예: 사용자 멘션 응답)로 게시되어 handler 미진입
- [ ] **수정 (정방향)** — 식별된 원인에 따라:
- 가설 A: subtype 가드 완화 (bot_message 허용) + 본문 패턴으로만 트리거
- 가설 B: silent fail 로그 강화 + 라벨 idempotency (이미 approved 라벨 있으면 skip)
- 가설 C: spec_gate_pending.json에 Raphael 발송 브리핑도 기록되도록 통일 (또는 handler 진입 조건 본문 패턴 단독화)
- [ ] **중복 트리거 방지** — 동일 티켓에
approved라벨이 이미 있으면 skip (재진입 안전성) - [ ] **회수 메커니즘 (역방향 fail-safe, 신규)** — 사용자가 자동 진행을 뒤집을 수 있는 경로
- 사용자 👎 리액션 감지 → approved 라벨 제거 + 진행 중 execution worktree 중단 신호
- 또는 [CANCEL] 코멘트 패턴 감지 → 동일 처리
- 진행 단계별 회수 가능성: ①approved 직후 (라벨만 제거) ②execution worktree 생성 후 (라벨 제거 + 워크트리 강제 종료) ③PR 생성 후 (라벨 제거 + PR 자동 close 또는 사용자 결정 위임)
- 회수 동작 시 Jarvis가 사용자 DM으로 알림 발송 (어느 단계까지 진행됐는지 + 회수 결과)
AC (이슈 본문 v2, 2026-05-22 갱신)
- [ ] Raphael 발송 [Spec Gate] 브리핑이 #agent-approvals에 도착하면 Jarvis 자동 검토가 실행됨
- [ ] Jarvis [APPROVED] 응답 직후 Linear 티켓에
approved라벨이 자동 부여됨 - [ ] 동일 응답에 👍 리액션이 자동 추가됨
- [ ] execution_scan_loop이 approved 라벨 픽업해 worktree 생성·SDD 파이프라인 실행
- [ ] **fail-safe**: 사용자 👎 리액션 또는
[CANCEL]코멘트 시 approved 라벨 즉시 제거 + 진행 중 execution worktree 중단 (또는 사용자 DM 경고) - [ ] 회수 동작이 Slack DM(Jarvis → 사용자)으로 알림 발송됨
Out of Scope
- HAN-168 (별도 진행)
- HAN-183 (Raphael 컨텍스트 내 이슈번호 파싱) — 본 이슈는 Raphael이 이미 [Spec Gate] 형식으로 발송했다는 전제
- spec_gate.py 자동 발송 경로 — 정상 작동 중, 손대지 않음
Decisions
| 결정 | 근거 |
|---|---|
| HAN-268 / HAN-168 분리 진행 | 코드 surface·트리거 메커니즘·실패 본질 모두 상이. premature abstraction 회피 |
| HAN-168은 priority Low 유지 + 본 작업 머지 후 별도 진행 | DM fallback은 alert noise만 못 받는 수준, 채널 모드는 정상 동작 (영향 한정) |
| HAN-268 재현 검증을 1단계로 명시 | 이슈 본문 "근본 원인 (추정)" 3가지 가설이 코드 리딩만으로는 확정 불가 — 실제 로그 확인 필요 |
| 본문 패턴 단독 트리거 vs spec_gate_pending.json 통일 — 미정 | 재현 검증 결과 보고 결정. 단독화가 더 simple하지만 state 추적 측면에서 pending.json 통일이 안전할 수 있음 |
Risks
| 위험 | 영향 | 대응 |
|---|---|---|
| 재현 검증 불가 (Raphael persona가 [Spec Gate] 브리핑을 발송한 실제 사례가 1건만 있음) | 가설 확정 어려움 | 인위적 재현 — Raphael bot 토큰으로 #agent-approvals에 [Spec Gate] 형식 메시지 발송 후 로그 관찰 |
| 핸들러 수정이 spec_gate.py 정상 흐름에 회귀 | 자동 승인 파이프라인 stall | 기존 test_approval_handler.py 6 TC 전부 통과 확인 + Raphael 발송 시나리오 TC 추가 |
| Jarvis 자체 [APPROVED] 응답이 handler에 재진입 (Jarvis가 자기 메시지에 또 [Spec Gate] 패턴 매칭) | 무한 루프 | Jarvis 응답 텍스트에는 `*[Spec Gate]*` 패턴 없음 — `[APPROVED]`만 포함. 안전. 다만 보강 가드로 author bot_id == 자기 자신이면 skip 검토 |
| **전체 자동화로 인한 silent 진행** — 사용자가 인지 못 한 채 잘못된 티켓 execution 진입 | PR 생성 / main 변경 등 되돌리기 어려운 부작용 | 회수 메커니즘이 fail-safe로 동작. Jarvis 자동 승인 시점에 사용자 DM 1줄 알림 ("HAN-XXX 자동 승인됨 — 👎로 회수 가능") 검토 |
| 회수 메커니즘 race condition — execution worktree가 회수 신호 전에 PR 생성 완료 | 회수 불가 상태 도달 | execution 단계별 회수 가능성 명확히 (approved / worktree-생성 / PR-생성) + 단계별 회수 동작 정의 |
Linked Issues
- HAN-168 (분리 — 별도 진행)
- HAN-174 (related — Jarvis 자동 검토 핸들러 원본)
- HAN-182 (related — set_thread_owner 누락, 비슷한 카테고리)
- HAN-183 (related — Raphael 이슈번호 파싱)
- HAN-252 (재현 사례 — 2026-05-22)
Reference
~/.claude/projects/-Users-hangman-Workspace/memory/reference_agent_approvals_pipeline.md~/.claude/projects/-Users-hangman-Workspace/memory/reference_slack_api_patterns.md(Slack IM 채널 2인 멤버 제약)- 코드:
bridge.py:1817-1867(handler),bridge.py:1888-1924(on_message 분기),spec_gate.py:191-235(scan_once),state/spec_gate_pending.json
Result (작업 완료 후 작성)
완료된 것
(TBD — implement 단계에서 작성)
배운 것
(TBD)
링크
- PR: (TBD)
- Linear: https://linear.app/hangman-lab/issue/HAN-268