report HAN-612 jini final 2026-06-09

[HAN-612] Pantheon 이미지 첨부 인식 갭 — 부검 보고서

TL;DR


Context — 타임라인

시각 사건 상태
2026-06-07 17:38 KST 사용자가 #pantheon-rd 스레드에 이미지 첨부 → jini가 못 봄 → #problem-report에 jini → Jarvis 우회 보고 첫 신호
2026-06-07 17:51-18:34 wansu가 plan 작성 → HAN-489 발의 → PR #237 구현 → 446 passed, 2 skipped "구현 완료"
2026-06-08 17:14-17:16 재배포 후 HAN-489 image attachment 8/8 passed, 전체 suite 478 passed, regression 없음 → Linear HAN-489 Done (pseudo) Done
2026-06-08 21:35 KST jini 봇이 사용자 첨부 PNG 수신 → [slack_image] decode_failed file_id=F0B8KCA1HV5 name=image.png mime=image/png size=57414image_attachments=[] → Claude가 텍스트만 응답 진짜 첫 실패
2026-06-09 사용자가 동일 스레드에서 "또 이미지 못 읽음" 신호 → jini가 첫 답으로 "Gemini swap이 의도된 동작" 가설 제시 → 사용자 "왜 Gemini가 등장?" 반문 진단 분리 시작
2026-06-09 jini가 #problem-report 원 스레드 직접 fetch → "HAN-489 Done의 실체 = pytest only / end-to-end 갭" 정리 → HAN-612 발의 (Priority High, relatedTo HAN-489) 처방 분기

진단 — 표면 vs 진짜

표면

"이미지 첨부했더니 봇이 못 봄. HAN-489 Done이라고 어제 들었는데 회귀 아닌가요?"

이 표면에서 Gemini가 등장하는 이유가 같이 의심됐다. 첫 답에서 bridge.py:1713의 자동 swap이 의도된 동작이라는 코드 설명까지 잘 짚었지만 — 그 자체는 사고의 본질이 아니었다.

진짜 root cause — 2개 레이어

Layer A — magic byte strict gate. bridge.py_looks_like_image()는 PNG \x89PNG\r\n\x1a\n / JPEG \xff\xd8\xff / WebP RIFF magic byte를 bytewise 검증한다. 통과 못 하면 attachment를 attachments 리스트에 안 담는다. 어제 21:35 사용자가 첨부한 57KB PNG가 이 게이트를 통과 못 했다. 가능한 원인:

Layer B — Gemini swap 분기가 attachments=[] 케이스에 발동 안 함. bridge.py:1713image_attachments and name != "gemini"에서만 자동 Gemini swap을 건다. Layer A에서 silent drop 되면 Gemini 라우팅 자체가 발동 안 한다. 결과적으로 페르소나(Jini = Claude sonnet)에 텍스트만 도달.

즉 사용자가 본 현상 "또 이미지 못 봄" = Layer A silent drop + Layer B 분기 우회의 합작이다.

HAN-489 "Done"의 함정

HAN-489 / PR #237은 fetch → MIME → magic byte → Gemini 입력 경로를 깔았다. 검증은 두 단계로 떨어졌다:

이 갭은 회귀가 아니다. 처음부터 닫힌 적이 없는 범위 갭이다. 그래서 HAN-489 Done이 잘못된 게 아니라 — Done의 정의가 jokingly 좁았다.


처방 — HAN-612 3 hop

HAN-612 (Priority High, Backlog, relatedTo HAN-489).

Hop 1. providers/claude.py, providers/codex.py무수정. 현재의 del image_attachments 그대로 둔다. 페르소나 정체성 보존.

Hop 2. bridge.py:1713swap 분기caption prepend 분기로 교체:

Hop 3. _looks_like_image 게이트를 soft-fail로 완화:

검증 기준 (real end-to-end)

부수 효과

페르소나 정체성 swap이 같이 닫힌다. "이미지 첨부하면 왜 Gemini가 등장?" 사용자 질문 자체가 사라진다.


학습 — 두 가지

학습 1 — "Done"의 정의는 pytest와 분리되어야 한다

pytest 통과 ≠ end-to-end 검증. HAN-489는 이 패턴의 표본 사례다.

학습 2 — Slack 페르소나 톤이 메인 권한을 self-cap 시키지 말 것

오늘 사용자가 "이 스레드 랩업해주세요"라고 했을 때, jini는 Slack 메시지 정리만 했고 /wrap-up 스킬을 호출하지 않았다. 이 세션은 사실 메인 Claude Code + jini emulating이라 Write/Edit/Skill 권한이 다 있는데, "Slack mrkdwn 3-6줄"·"짧게" 톤 룰에 잠겨 스킬 호출 분기를 안 탔다.


검증 질문 (사용자에게)

  1. HAN-612 처방의 caption inject 위치를 "user message 앞 inline 인용 블록"으로 잡는 게 맞을지 — 톤 오염을 최소화하려면 system note 쪽이지만, "봇이 뭘 봤는지" 디버깅 가시성을 위해 inline 인용이 본 사고의 본질과 맞아 보인다.
  2. 학습 2의 처방을 Pantheon 페르소나 시스템 프롬프트에 가드로 박을지, 아니면 메인 세션 /wrap-up 스킬 본문에 "랩업 키워드 = 자동 트리거" 룰을 박을지.

링크