# task-2712+2 — PR #161 Gemini unresolved 4건 same-PR micro-fix evidence

- **팀/작성자**: dev4-team / 비슈누(Vishnu) — task-2712 원작자
- **PR**: #161 (CI_PASS_BUT_GEMINI_HIGH_BLOCKED)
- **base head**: `58a15618` (branch task/task-2712-dev4) — 이 head 위 1 commit
- **레벨**: Lv.2 micro-fix (expected_files 내부, behavior/marker schema 불변)
- **chair_authorization**: 회장 verbatim 2026-05-30 same-PR micro-fix 인가
- **검증 일시**: 2026-05-30 (server time)

각 finding → 변경 라인 → 검증 결과를 1:1 로 기록한다.

---

## ① dispatch.py — HIGH (`_REPO_ROOT` / broad except 가 callback silent disable)

**Gemini**: `_REPO_ROOT` is used … never defined … broad `except Exception:` raises NameError
silently caught, causing `_fcb_write_handoff` 및 `_verify_bot_spawn` to always be None.
This silently disables the entire failure callback mechanism.

**사실 확인**: base `58a15618` 시점 `dispatch.py:25` 에 이미 `_REPO_ROOT = Path(__file__).resolve().parent`
가 정의되어 있다(task-2388 facade 분리 `0137097` 에서 도입, `from pathlib import Path` 도 line 22 기존 존재).
따라서 NameError 자체는 발생하지 않는 상태였고, Gemini 가 지적한 **실질 위험은 broad `except Exception:` 이
NameError 등 programming error 까지 흡수하여 callback 을 무신호로 비활성화**하는 점이다.

**fix (task 인가 옵션 a — "import-guard except 가 NameError 를 가리지 않게")**:
- `dispatch.py:41` `except Exception:` → `except ImportError:` 로 협소화.
- 의도된 모듈 부재(fresh checkout = ModuleNotFoundError ⊂ ImportError)는 여전히 흡수하여 graceful None 유지.
- NameError/AttributeError 등 programming error 는 더 이상 삼켜지지 않고 **전파**되어 callback silent disable 방지.
- `_REPO_ROOT` 정의는 기존(line 25) 그대로 — 중복 정의 추가 없음(unrelated 변경 0).

**변경 라인**:
```
-except Exception:  # pragma: no cover
+except ImportError:  # pragma: no cover (모듈 부재만 흡수 · NameError 등 programming error 는 전파)
```

**검증**:
- `python3 -c "import dispatch"` → IMPORT OK, NameError 0.
- shim(`dispatch.py`) 직접 로드 → `_fcb_write_handoff is None? False`, `_verify_bot_spawn is None? False`
  → happy path 에서 callback 메커니즘이 실제로 wiring 됨(silent disable 아님) 확인.
- dispatch main behavior 불변(코드 경로 무수정, except 절만 협소화).

---

## ② scripts/finish-task.sh — HIGH (INT/TERM trap exit 누락)

**Gemini**: trapping INT or TERM without calling exit … script resumes after handler …
append `exit 130` (INT) and `exit 143` (TERM).

**fix**: `finish-task.sh:41-42` INT trap 끝에 `; exit 130`, TERM trap 끝에 `; exit 143` 추가.
EXIT trap(line 40) 의 `_emit_failure_envelope … ; cleanup_timer` 동작은 보존. INT/TERM 에서 exit 호출 시
EXIT trap 이 1회 더 fire 되더라도 marker idempotent(exactly-one)로 안전.

**변경 라인**:
```
-trap '… "$TASK_ID" -2 "SIGINT"  … || true' INT
-trap '… "$TASK_ID" -15 "SIGTERM" … || true' TERM
+trap '… "$TASK_ID" -2 "SIGINT"  … || true; exit 130' INT
+trap '… "$TASK_ID" -15 "SIGTERM" … || true; exit 143' TERM
```

**검증**:
- `bash -n scripts/finish-task.sh` → syntax PASS.
- 신호 behavioral 테스트(동일 3-trap 구조 재현): SIGINT → exit **130** + marker **정확히 1개**,
  SIGTERM → exit **143** + marker **정확히 1개**. EXIT trap re-fire 는 multi-fire 가드로 no-op(exactly-one 보존).
- success path `.done` 동작 변경 없음.

---

## ③ scripts/harness/v36/before_exit_guard_hook.sh — MEDIUM (events dir mkdir)

**Gemini**: events dir 부재 시 redirect `2>>$_FCB2712_EVENTS_DIR/…stderr-emit.log` 실패 → python 미실행.
`mkdir -p` 필요.

**fix**: `_emit_failure_envelope` 내 `[ -z "$task_id" ] && return 0` 직후, 어떤 file 연산/redirect 보다
먼저 `mkdir -p "$_FCB2712_EVENTS_DIR" 2>/dev/null || true` 수행(best-effort 비차단). marker schema 불변.

**변경 라인**:
```
     [ -z "$task_id" ] && return 0
+
+    # task-2712 Gemini MEDIUM #3: events dir 부재 시 redirect/append 실패 → python
+    # 미실행(marker 미박제) 방지. 어떤 file 연산/redirect 보다 먼저 dir 보장 (비차단).
+    mkdir -p "$_FCB2712_EVENTS_DIR" 2>/dev/null || true
```

**검증**:
- `bash -n before_exit_guard_hook.sh` → syntax PASS.
- L1 smoke 를 **존재하지 않는 events dir** 로 실행 → dir 자동 생성 + marker 정상 박제(SCOPE/QC/CRASH 모두 WRITTEN).
  fix 전이라면 redirect 타깃 dir 부재로 python 미실행 → marker 미박제였을 경로.

---

## ④ scripts/harness/v36/failure_callback_dispatcher.py — MEDIUM (file handle with)

**Gemini**: `json.load(open(path))` leaks fd … use `with`.

**fix**: `detect_bypass_via_count_mismatch` 내 line 236 `json.load(open(path, encoding="utf-8"))` →
`with open(path, encoding="utf-8") as f: envelope = json.load(f)` 로 변경. 기존 `except Exception: envelope = {}`
보존. 로직/감지 결과 변경 없이 resource handling 만 개선(파일 내 유일 occurrence).

**변경 라인**:
```
-                envelope = json.load(open(path, encoding="utf-8"))
+                with open(path, encoding="utf-8") as f:
+                    envelope = json.load(f)
```

**검증**:
- `python3 -m py_compile failure_callback_dispatcher.py` → COMPILE OK.
- `pytest tests/test_failure_callback_before_exit_guard_2712.py` 23/23 PASS
  (`test_detect_bypass_count_mismatch` 포함 — 로직 동등성 확인).

---

## 필수 검증 종합 (전부 PASS)

1. `python3 -m pytest tests/test_failure_callback_before_exit_guard_2712.py -v` → **23 passed** (23/23).
2. L1 hook smoke 5종: SCOPE_GUARD_FAIL ✔ / QC_FAIL ✔ / CRASH(supervisor-crash-marker) ✔ /
   SUCCESS no-op(marker 0) ✔ / exactly-one(count=1) ✔ → **pass=6 fail=0** (fix#3 mkdir 검증 포함).
3. `python3 -c "import dispatch"` → IMPORT OK, **NameError 0**.
4. `bash -n scripts/finish-task.sh` → **syntax PASS** (hook 도 PASS).
5. 본 evidence md 작성 — 4 finding × (변경 라인 + 검증) 기록 완료.
6. 변경 파일 = expected_files 만(dispatch.py · finish-task.sh · before_exit_guard_hook.sh ·
   failure_callback_dispatcher.py + 본 evidence md). 외부/forbidden path 변경 0.

## 불변 보존 / 금지 준수
- behavior·marker schema 변경 0 · unrelated refactor 0 · expected_files 밖 수정 0.
- task-2713 혼입 0 · task-2710/2711/2706~2709 evidence 무변경 · caveat 3건 무삭제.
- force push/rebase/admin merge/merge 0. dev bot push 미수행(ghs_ App token 부재) — 로컬 commit 까지만.
  push 는 ANU 가 OWNER PAT fast-forward normal push 로 수행.
