# incident-2026-05-05-gemini-trigger — Gemini Review Trigger 조사

- 작업 ID: task-2464 (조사 전용 — read-only)
- 위임팀: dev6-team (페룬)
- 보고 일시: 2026-05-06
- 대상 PR: #29 (`task/task-2463-dev6` → `main`)
- 결론: **두 개 별개 결함이 동시에 작동** — (1) `GEMINI_API_KEY` 시크릿 부재, (2) `gemini-code-assist[bot]` 외부 App 활동 중단(2026-05-04~). task-2463이 도입한 게이트는 의도대로 차단.

⚠️ 본 문서는 read-only 조사 산출물. 어떤 시크릿 값도 포함되지 않음. 코드/설정/PR 변경 0건.

---

## 0. Executive Summary

PR #29에서 두 검사 fail:
- **`gemini-review-gate`** (자체 스크립트 `scripts/gemini_review_gate.py`): exit 1
- **`phase3-merge-gate`** (task-2463 P0-3 신규 게이트): exit 1

근본 원인은 분리된 두 시스템이 모두 비정상 상태이기 때문:

| 시스템 | 정체 | 의존 자원 | 현재 상태 |
|---|---|---|---|
| A. `gemini-review-gate` | 자체 스크립트 (Gemini API 직접 호출) | `GEMINI_API_KEY` (repo secret) | secret **부재** (총 0건) |
| B. `phase3-merge-gate` | 외부 App `gemini-code-assist[bot]`의 PR review 수 검증 | GitHub App `gemini-code-assist` | 봇 활동 **중단** (2026-05-04 00:21 UTC~) |

task-2463의 변경(`neutral → failure` + review-count 0 차단)은 의도대로 동작 중. **게이트가 부서진 것이 아니라, 게이트가 바라보는 두 시스템이 부서져 있다.**

---

## 1. 타임라인 (PR #29)

| 시각(UTC) | 사건 | 출처 |
|---|---|---|
| 2026-04-15 06:26:59 | `gemini-code-assist[bot]` 첫 PR 코멘트 (PR #1) | `repos/.../pulls/comments` |
| 2026-05-03 22:59:13 | PR #15 open (`[task-2440] GitHub raw evidence + SCQA 보고서`) | `pulls/15` |
| 2026-05-03 23:00:43 | **`gemini-code-assist[bot]` 마지막 활동** (PR #15 review state=COMMENTED) | `pulls/15/reviews` |
| 2026-05-03 ~ 2026-05-04 | (외부 봇 활동 중단 — 원인은 본 조사 범위 외, 가설은 §6) | — |
| 2026-05-04 00:21:39 | PR #16 open — 이후 모든 PR(#16~#29) 봇 review 0건 | `pulls/{16..29}/reviews` |
| 2026-05-05 07:05:36 | PR #24 (task-2454) open — Gemini 리뷰 5분+3분 TIMEOUT 후 수동 머지 (`memory/reports/task-2454.md:140`) | task-2454 보고서 |
| 2026-05-05 (사이) | task-2463 페룬: `gemini_review_gate.py` 강화 (`neutral → failure`) + `phase3-merge-gate` 추가 | task-2463 산출물 (브랜치 `task/task-2463-dev6`) |
| 2026-05-05 16:01:40 | PR #29 open | `pulls/29` |
| 2026-05-05 16:01:51 | `phase3-merge-gate` step 시작 (`actions/runs/25387518453`) | jobs API |
| 2026-05-05 16:02:19 | `phase3-merge-gate` FAIL — `Gemini review count: 0` → exit 1 | job 74452670318 logs |
| 2026-05-05 16:02:00 | `gemini-review-gate` step 시작 | jobs API |
| 2026-05-05 16:02:43 | `gemini-review-gate` FAIL — `[GEMINI-GATE] FAIL — review not executed (reason: GEMINI_API_KEY missing)` → exit 1 | job 74452670283 logs |
| 2026-05-05 16:02:44 | CI 워크플로우 conclusion=`failure` 확정 | runs API |

---

## 2. 5개 조사 항목 — Evidence

### 2.1 GEMINI_API_KEY secret 존재 여부 — **부재**

**명령**:
```bash
gh api repos/Jeon-Jonghyuk/dev_workspace/actions/secrets
gh api repos/Jeon-Jonghyuk/dev_workspace/actions/organization-secrets
gh api repos/Jeon-Jonghyuk/dev_workspace/actions/secrets/public-key
```

**결과** (값 0byte):
```
{"total_count":0,"secrets":[]}                     ← repo level
{"total_count":0,"secrets":[]}                     ← org level (개인 repo이므로 N/A)
{"key_id":"3380204578043523366","key":"VDhsuwBR…"} ← 공개 암호화 키 (정상)
```

→ repo/org 어느 레벨에도 secret이 등록되어 있지 않음. `GEMINI_API_KEY` 미존재 확정.

**workflow에서 secret 참조 위치**:
- `.github/workflows/ci.yml:125` — `GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}`
- 위 env가 `gemini-review-gate` job에만 주입됨 (다른 job에는 없음)

**runtime evidence** (job 74452670283 step 로그):
```
env:
  GEMINI_API_KEY:           ← 빈 값
  GH_TOKEN: ***
```

### 2.2 Gemini Code Assist app 설치/권한 확인

**명령 + 결과**:
- `gh api /users/gemini-code-assist[bot]` → 봇 사용자 존재 (id=176961590, type=Bot, html_url=`github.com/apps/gemini-code-assist`)
- `gh api /users/gemini-code-assist` → 일반 user account (id=200291788, blog=`developers.google.com/gemini-code-assist/docs/review-github-code`)
- `gh api /repos/.../installation` → `401 A JSON web token could not be decoded` (App JWT 필요 — repo PAT로는 접근 불가, 정상)

**활동 이력 — gemini-code-assist[bot] PR review 수**:

| PR # | open 시각 | gemini reviews | 비고 |
|---|---|---|---|
| #1 | 2026-04-15 | 1 | 정상 |
| #5 | 2026-05-02 | 1 | 정상 |
| #10 | 2026-05-02 | 1 | 정상 |
| #13 | 2026-05-03 | 1 | 정상 |
| #14 | 2026-05-03 | 1 | 정상 |
| #15 | 2026-05-03 | 1 | **마지막 정상 PR** |
| #16 | 2026-05-04 00:21 | **0** | 봇 활동 중단 |
| #17 | 2026-05-04 00:57 | 0 | |
| #18 | 2026-05-04 06:24 | 0 | |
| #19~#23 | 2026-05-04~05 | 0 | |
| #24 | 2026-05-05 07:05 | **0** | task-2454 (TIMEOUT 보고) |
| #25~#28 | 2026-05-05 | 0 | |
| #29 | 2026-05-05 16:01 | **0** | 본 조사 대상 |

**마지막 활동**: `gemini-code-assist[bot]` review COMMENTED on PR #15 at **2026-05-03T23:00:43Z**.
이후 PR 14건(#16~#29) 연속 review 0건. 단순 누락이 아닌 **체계적 중단** 패턴.

**권한**: 봇 type=Bot이고 PR review를 정상 수행한 이력 존재 → review:write/read 권한은 보유 (단, 현재 비활성).

### 2.3 Workflow event 트리거 조건

**`gemini-review-gate` (`.github/workflows/ci.yml:119-154`)**:
- `on: pull_request` (`.github/workflows/ci.yml:3`) + `merge_group` (`ci.yml:5`)
- `pull_request_target` **미사용** → fork PR에서 secret **자동 차단** (보안 정상)
- fork-PR 명시 차단 조건은 없음
- `if: always()` (line 122) → 다른 job 실패와 무관하게 항상 실행
- env에 `GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}` 직접 주입 (line 125)

**PR #29의 트리거 만족 여부**:
- PR #29의 head는 같은 repo branch (`task/task-2463-dev6`) → fork PR 아님
- 따라서 secret 컨텍스트 접근 가능 (있다면)
- `pull_request` event로 정상 트리거됨 (job=`gemini-review-gate`가 실행됨이 evidence)

**`phase3-merge-gate`**:
- main의 `.github/workflows/*.yml`에 **존재하지 않음** (grep 0건)
- `task/task-2463-dev6` 브랜치에서 추가된 신규 job (PR #29 자체 검증 대상)
- task-2463 산출물 위치는 본 조사 forbidden (브랜치 worktree 접근 금지) — job 로그를 통해 동작만 확인:
  - `repos/Jeon-Jonghyuk/dev_workspace/pulls/$PR/reviews` 호출
  - login에 `gemini-code-assist` 포함된 review 수 카운트
  - `count=0`이면 `::error::phase3-merge-gate: gemini-code-assist review 0건 — merge 차단 (task-2463 P0-3)` → exit 1

### 2.4 PR #29 event timeline 확인

**run/job 메타** (`gh run list --branch task/task-2463-dev6`):
- run 25387518453 (CI workflow, conclusion=failure, head_sha=c97777f1)
- run 25387518367 (Task Guard workflow, conclusion=success)
- run 25387497349 (Task Guard, conclusion=success)

**run 25387518453 jobs**:
| job | conclusion | 시각 |
|---|---|---|
| cancel-kill-switch | success | 16:01:52 ~ 16:02:20 |
| qc-check | success | 16:01:59 ~ 16:02:33 |
| guard | success | 16:01:59 ~ 16:02:37 |
| hidden-path-audit | success | 16:02:00 ~ 16:02:38 |
| ci/guard | success | 16:01:52 ~ 16:01:55 |
| merge-safety-check | success | 16:02:00 ~ 16:02:33 |
| lock-in-check | success | 16:02:01 ~ 16:02:31 |
| **gemini-review-gate** | **failure** | 16:02:00 ~ 16:02:44 |
| **phase3-merge-gate** | **failure** | 16:01:51 ~ 16:02:22 |

**핵심 evidence — gemini-review-gate fail 메시지** (job 74452670283 raw log):
```
[GEMINI-GATE] FAIL — review not executed (reason: GEMINI_API_KEY missing)
{"name": "gemini-review-gate", "pr": 29, "sha": "c97777f1...",
 "state": "failure", "matches": [], "latency_ms": 0}
##[error]Process completed with exit code 1.
```

**핵심 evidence — phase3-merge-gate fail 메시지** (job 74452670318 raw log):
```
[phase3-merge-gate] Gemini review count: 0
##[error]phase3-merge-gate: gemini-code-assist review 0건 — merge 차단 (task-2463 P0-3)
##[error]Process completed with exit code 1.
```

**두 시스템의 분리**:
- 자체 스크립트(A) = Gemini API 직접 호출 → diff에 blocking 패턴 매칭 → check run 발행. 키 의존.
- 외부 App(B) = GitHub App이 PR open 시 webhook 수신 → 자체 백엔드에서 분석 → review 작성. App 인증 의존.
- 둘은 **서로 알지 못하며, 어느 한 쪽이 통과해도 다른 쪽은 따로 통과해야 함**.

**task-2454(PR #24)와의 비교**:
- 공통점: 두 PR 모두 `gemini-code-assist[bot]` review 0건 (외부 봇 활동 중단 후)
- 차이점:
  - PR #24 시점에는 `phase3-merge-gate`가 main에 부재 → 차단 안 됨, 수동 머지로 통과 (task-2454 보고서:140 "TIMEOUT, 수동 승인 후 머지")
  - PR #29 시점에는 `phase3-merge-gate`가 task-2463 브랜치에 존재 → review 0건 즉시 차단
- 동일한 외부 봇 부재 상태에서 정책만 강화된 결과 — task-2463의 게이트 강화가 task-2454의 silent pass 재발을 방지함

### 2.5 `scripts/gemini_review_gate.py` 인증 요구사항

**main 브랜치 코드** (`scripts/gemini_review_gate.py`, 327 lines):
- `--publish-check` 모드: `gate()` 함수 (라인 223-308)
- `call_gemini()` (라인 124-187): `os.environ.get("GEMINI_API_KEY")` 미존재 시 `{"ok": False, "error": "GEMINI_API_KEY missing"}` 반환 (line 144-146)
- 실패 처리 (line 272-274 main): `if not gemini_result["ok"]: conclusion = "neutral"` → exit 0 (CI 통과)
- 즉 **main 코드만으로는 PR #29가 통과**됐어야 함 (neutral)

**task-2463 브랜치(`task/task-2463-dev6`) 변경**:
- `--allow-neutral` 플래그 추가 (line 329, DEBUG 전용)
- `if allow_neutral: conclusion="neutral"` else `print("[GEMINI-GATE] FAIL — review not executed", file=sys.stderr)` + exit 1 (line 281)
- main에는 `[GEMINI-GATE] FAIL` 문자열이 0건 (`grep -n` 확인)

**두 모드의 차이**:
- 검증 모드: PR review 수만 확인 (외부 봇 동작 검증) — `phase3-merge-gate`가 담당
- 자체 실행 모드: API 키로 Gemini 직접 호출, diff 리뷰 — `gemini-review-gate` job이 담당
- task-2463은 **자체 실행 모드의 fail-open(neutral)을 fail-closed(failure)로 강화** — 회장 명시("Gemini 없이 Phase 3 통과시키지 말 것")의 코드화

**현재 워크플로우 의도 추론**:
- 자체 스크립트는 **외부 봇이 부재하거나 일시 장애일 때의 fallback** 역할 의도로 보임
- 그러나 시크릿이 없으면 fallback도 동작 못 함 → 두 시스템 모두 의존성이 깨짐
- task-2463 페룬 변경은 이 사실을 표면화시킴 (good — silent pass 차단)

---

## 3. 근본 원인 분석

### 3.1 결함 1 — `GEMINI_API_KEY` repo/org secret 부재

- repo level: `total_count=0`
- org level: `total_count=0` (개인 repo이므로 org 자체 없음)
- 워크플로우는 `secrets.GEMINI_API_KEY` 참조 — 빈 값으로 env 주입됨
- 자체 스크립트 line 144-146이 빈 값 감지 → `error="GEMINI_API_KEY missing"`
- task-2463 변경으로 이 상태가 exit 1 (이전엔 neutral)

**시점**: 시크릿이 한 번도 등록되지 않았는지, 회전 후 누락인지 본 조사로는 단정 불가. 단 `gemini_review_gate.py`가 main에 도입된 시점부터(task-2440 라인 추정) 항상 누락 상태였을 가능성이 큼 — task-2454 보고서의 "TIMEOUT" 표현은 외부 봇 부재 + 자체 스크립트 neutral 합산 결과로 해석 가능.

### 3.2 결함 2 — `gemini-code-assist[bot]` 외부 App 활동 중단

- 마지막 활동: 2026-05-03T23:00:43Z (PR #15)
- 이후 14개 PR 연속 review 0건
- 봇 사용자 자체는 존재하며 type=Bot
- repo 차원의 webhook 수신 가능 여부는 본 조사로 확정 불가 (`/repos/.../installation`은 App JWT 필요)

**가능 원인 (가설, 검증 별도 task)**:
- Gemini Code Assist 무료 한도 초과 (Google 측 quota — `gemini_rate_tracker.json`은 자체 스크립트 한도이고 외부 App 한도와 무관, task-2454 보고서:140 참조)
- App 권한 변경/제거 (회장이 모르게)
- App 백엔드 일시/지속 장애
- 빌링 상태 변경

### 3.3 결함 3 (보강) — task-2463 변경이 두 결함을 표면화

- 이전: 두 결함 존재했지만 fail-open 정책으로 silent pass (PR #16~#28 머지된 케이스 다수)
- task-2463 P0-3: review 0건 차단 + neutral→failure
- 결과: PR #29가 의도대로 차단됨 — 게이트가 정상 작동 중

**이는 결함이 아닌 정상 동작.** task-2454 사고("TIMEOUT 후 수동 머지")의 재발을 방지하는 안전장치.

---

## 4. task-2454 사고와의 비교

| 항목 | task-2454 (PR #24, 2026-05-05 07:05) | task-2464/PR #29 (2026-05-05 16:01) |
|---|---|---|
| `gemini-code-assist[bot]` review 수 | 0 | 0 |
| 외부 봇 부재 (root) | 동일 (#16 이후 패턴 동일) | 동일 |
| `GEMINI_API_KEY` secret 상태 | 부재 (당시도 0건 추정) | **부재 (확정)** |
| 자체 스크립트 결과 | neutral (당시 main 코드) → silent pass | failure (task-2463 변경) → 차단 |
| `phase3-merge-gate` 존재 | 미배포 | task-2463 브랜치에 존재 |
| 머지 결과 | **수동 승인 후 머지** (task-2454 보고서:140) | **차단 (open)** |
| 차이의 원인 | 게이트가 약함 (fail-open) | 게이트가 강함 (fail-closed) |

**공통**: 외부 봇 부재 + 시크릿 부재 (동일한 두 결함)
**차이**: task-2463의 게이트 강화로 task-2464에서는 차단됨 (silent pass 재발 방지)

→ 두 사고는 **같은 인프라 결함의 다른 표면 노출**. 근본 원인은 동일 (외부 봇 + 시크릿).

---

## 5. 수정 옵션 (코드 변경 0건)

> 본 task는 read-only — 옵션 명세만. 실제 적용은 별도 task에서.

### Option A — `GEMINI_API_KEY` repo secret 추가 (자체 스크립트 살리기)

- **절차**:
  1. Google AI Studio에서 Gemini API 키 발급 (회장)
  2. `gh secret set GEMINI_API_KEY -R Jeon-Jonghyuk/dev_workspace --body "<key>"` 또는 GitHub UI Settings → Secrets and variables → Actions → New repository secret
  3. PR #29 재실행 (head SHA push 또는 `gh pr ready` 등으로 workflow 트리거)
- **필요 권한**: repo admin (회장)
- **장점**: 자체 스크립트(`gemini-review-gate`)가 외부 봇 부재 시에도 동작 — 진정한 fallback이 됨
- **단점**: API 비용 발생 (gemini-2.5-pro 호출 — 호출 빈도 통제 필요), `phase3-merge-gate`(B 시스템)는 여전히 fail
- **상태**: A만 단독으로는 PR #29 머지 불가 (phase3-merge-gate가 외부 봇 review를 검증하므로)

### Option B — `gemini-code-assist[bot]` 외부 App 재활성화 조사 + 복구

- **절차**:
  1. https://github.com/apps/gemini-code-assist 설치 상태 확인 (회장 권한)
  2. https://developers.google.com/gemini-code-assist 콘솔에서 quota/billing/installation 상태 확인
  3. 무료 한도 초과면 유료 plan 또는 quota reset 대기
  4. App 미설치/권한 부족이면 재설치
  5. 복구 후 PR #15 ~ #29 중 한 건에 sync 트리거 (`/gemini review` 코멘트 또는 push)
- **필요 권한**: repo admin + Google Cloud account
- **장점**: 진짜 의미의 외부 코드 리뷰 복구 — `phase3-merge-gate`가 본래 의도대로 동작
- **단점**: 외부 의존, quota 한계, 봇 동작 시점 통제 어려움

### Option C — Break-glass 절차 (수동 우회) 정의

- **절차**: `phase3-merge-gate`가 차단된 상태에서 회장/아누 admin이 수동 머지하는 절차 문서화
  - admin의 `gh pr merge --admin` 사용 (ruleset bypass 권한)
  - 사용 사유 + 영향도 + audit log를 별도 파일에 기록 (예: `memory/specs/break-glass-log.md`)
- **필요 권한**: repo admin
- **장점**: A/B 복구가 지연될 때도 critical fix 머지 가능
- **단점**: 우회 남용 시 task-2454 사고 재발 — 사용 횟수 한도/리뷰 의무화 필요

### Option D — workflow event 변경 (`pull_request` → `pull_request_target` + 외부 검증)

- **불권장**. `pull_request_target`은 fork PR에도 secret 노출되어 보안 위험. 본 repo는 fork PR이 적지만, 정책적으로 회피.
- 대안: `repository_dispatch`로 외부 trigger를 받는 방식 — 코드 변경 필요하므로 본 옵션 범위 외.

### Option E — 자체 스크립트와 외부 App 일원화 (코드 변경 필요)

- task-2463의 두 게이트(A/B)를 단일 게이트로 통합:
  - 외부 봇 review 1건 OR 자체 스크립트 success → 통과
  - 둘 다 실패 → 차단
- 장점: 단일 점검 지점, 결함 시 자동 fallback
- 단점: **코드 변경 필요** → 본 task 범위 외, 별도 task 필요. 또한 회장 의도("둘 다 강화")와 어긋날 수 있음.

### 권장 우선순위

1. **A + B 동시 진행** (별도 task로): A는 즉시 가능(secret 추가만), B는 외부 의존이므로 병렬 조사. 둘 중 하나라도 복구되면 절반은 풀림. 둘 다 복구되면 정상.
2. **C는 보조 수단**: A/B 복구 지연 중 critical hotfix 처리용. 정상 운영에서 사용 금지.
3. **E는 장기 검토**: A/B가 자주 깨진다면 일원화 가치 있음. 안정적이라면 현 분리 정책 유지.

---

## 6. ★ 권고 — Gemini 없이 Phase 3 통과시키지 말 것

**회장 명시 사항을 본 보고서에서 재확인**:
- task-2463의 `phase3-merge-gate` + `gemini-review-gate(failure 모드)` 변경은 **유지**해야 함
- 어떤 이유로도 `--allow-neutral`을 CI에 추가하지 말 것 (DEBUG only)
- 어떤 이유로도 `phase3-merge-gate`를 if: false 또는 continue-on-error로 만들지 말 것

**이유**:
- task-2454 사고의 본질: 외부 봇 부재 + 자체 스크립트 fail-open → silent pass → 머지 후 검증 미수행
- task-2463이 막은 것: 같은 silent pass 패턴 재현 — 회장 직접 진단대로 "merge 단계가 아니라 시작 단계와 검증 단계의 코드 가드 부재"가 root cause
- Gemini 게이트가 부서졌을 때의 정답은 **게이트를 약화**가 아니라 **인프라 복구** (Option A/B)
- 게이트 약화는 silent corruption의 입구

**예외 (회장 직접 승인 필요)**:
- 보안 hotfix가 외부 봇 복구를 기다릴 수 없을 때만 Option C(break-glass) 사용
- 사용 시 audit log 필수, 1주 이내 정상 게이트로 복귀 의무

---

## 7. 본 조사의 무결성 보장

- 본 task는 read-only — 코드/설정 0건 변경
- 시크릿 값 0byte 출력 (이름/존재 여부만 evidence로 사용)
- PR #29에 코멘트/리뷰/푸시 0건
- `gh pr merge` 등 변경 명령 0건 호출
- `git diff origin/main..HEAD --name-only` → 본 worktree에서 보고서 2건만 신규 (forbidden_paths 위반 0건)
- 다른 worktree (task-2454-dev*, task-2460-*, task-2461-*, task-2463-dev6) 접근 0건

evidence 출처:
- `gh api repos/Jeon-Jonghyuk/dev_workspace/actions/secrets`
- `gh api /users/gemini-code-assist[bot]`
- `gh api repos/.../pulls/{1..29}/reviews`
- `gh api repos/.../pulls/comments`
- `gh api repos/.../actions/runs/25387518453/jobs`
- `gh api repos/.../actions/jobs/{74452670283,74452670318}/logs`
- `gh api repos/.../contents/scripts/gemini_review_gate.py?ref=task/task-2463-dev6`
- `cat .github/workflows/ci.yml` (main 브랜치)
- `cat scripts/gemini_review_gate.py` (main 브랜치)
- `memory/reports/task-2454.md`
