마지막 업데이트 2026-09-19
장애 분석, 코드레벨 동작 흐름, 운영 리포트, 리뷰 문서를 한 화면에서 빠르게 찾는 내부 문서 허브입니다.
신고 대응, 시스템 이해, 최근 변경 확인처럼 자주 쓰는 경로를 먼저 배치했습니다.
AJ_CLYSMmWdT876(워커 AW_eg5RnPhNXC2r)이 생존한 15:08:03~18:12:48 동안 같은 ECS 태스크에 배정된 수업 job 47건을 CloudWatch @logStream 기준으로 전수 집계. ① 경고(inference is slower than realtime)가 찍힌 피해 job은 22건이고 전부 17:32:53 이후 — 17:32:53 이전 동거 24건은 경고 0건, 이후 23건 중 22건 피해(96%)라 동거·슬롯 점유는 필요조건일 수는 있어도 충분조건이 아님이 전수로 확정 ② job별 시작·종료·경고 건수·최대 누적 밀림(2.6~83.6초)·밀림 합계 표 수록, 좀비 자신은 712건·최대 161.8초·합계 19,735초 ③ 피해 구간 한복판 18:04:01의 AJ_D85H6ApFQSc7만 경고 0건인 예외 1건을 임계 요인 단서로 기록 ④ 수치 함정 정정 — 이 경고 로그는 3중 중복 출력되지 않는다(원본 1,231행을 (timestamp, job_id, delay)로 dedup해도 1,231행 그대로), 통념대로 1/3로 나누면 3배 과소 집계 ⑤ "같은 워커"는 ECS 태스크·프로세스 단위지 EC2 호스트 단위가 아니므로 호스트 경합 가설은 이 데이터로 확인·기각 불가 ⑥ Logs Insights 재현 쿼리 2종과 창 범위 함정(좀비일수록 시작 로그가 창 밖이라 목록에서 사라짐) 포함.
좀비 job 판별 — 반복되는 오해 3가지 (20분 로그 · 712건 · CPU 독식)
9/15 AI 미발화 후속으로, 좀비 job 로그를 읽을 때 반복되는 오진 3가지를 코드 경로와 함께 교정한 한 줄 요약 가이드. ① realtime input audio commit correlation quarantined; reconnecting은 고장 신호가 아니라 DEFAULT_MAX_SESSION_DURATION = 20 * 60(realtime/utils.py:62)에 따른 20분 주기 커넥션 재활용으로 정상 job도 동일하게 찍는다 — realtime_model.py:1426 타이머 만료 → :1175 finally의 quarantine → :2118 WARN → :1168-1170 즉시 재연결·chat_ctx 복원, agent.py:1355는 max_session_duration 미지정이라 기본값 적용. 판별 기준은 로그 유무가 아니라 그 뒤의 대화 활동 유무 ② inference is slower than realtime 712건은 'CPU 호출 횟수'가 아니라 1회 추론이 200ms(inference/vad.py:34)를 넘긴 경고 횟수로, 32ms 창·7,910초 기준 추론 호출은 약 247,000회이고 경고 비율은 0.29%에 불과하며 161.8s는 단건 소요가 아니라 inference/vad.py:359-364의 누적 밀림 — 건수는 생존 시간에 비례하므로 비교는 분당 발생률로 정규화 ③ '좀비의 CPU 독식'은 실측 미지지(좀비 5.4건/분 vs 피해 job 중앙값 51.9건/분, 17:20까지 동일 조건 정상)이며 확정된 기여는 슬롯 1칸 3시간 점유까지. 부록으로 _ppi_agent_load(agent.py:355-373)가 job 개수만 보고 CPU를 반영하지 않아 포화 태스크가 load 0.495로 계속 선택되는 구조와, 반대로 좀비 2개가 슬롯을 다 채우면 load = 0.99로 워커가 통째로 배정에서 빠지는 9/16 관측 수록.
9/15 핑퐁이가 말을 안 한 40분 — 좀비 job 슬롯 점유 쉬운 설명
2026-09-15 17:33~18:12 아동 12명·수업 job 24개에서 핑퐁이가 글자는 늦게 내보내되 소리는 끝내 내지 않은 사건을 사전지식 없이 읽을 수 있게 주방 비유와 그림 5장으로 설명한다. ① AI 일꾼(워커 AW_eg5RnPhNXC2r)은 화구 2개(동시 job 상한 2)인 주방이고, 테스트 계정 '테스트아리'의 방치 세션(좀비 job AJ_CLYSMmWdT876)이 15:08부터 3시간 5분 화구 하나를 차지해 그 일꾼에 들어온 수업 전부가 '두 번째 동시 job'이 됨 ② 시간표: 15:28 세션 고장·job 생존 → 16:00 실시간 미추종 경고 누적 → 17:20 마지막 정상 → 17:33~18:12 피해 → 18:12:48 방치 페이지 닫힘 즉시 회복(재시작·배포 없음) ③ 듣기(VAD 적체 6~84초, 처리율 1~6%) → 생각(LLM 글자 6.6~24초 지연 도착) → 말하기(tts_node_first_audio_frame 0건)로 '느림'과 '미도달'을 구분 ④ 확진: 손범 19방 중 실패 4방 전부 같은 일꾼, 정상 15방은 다른 일꾼 9명(우연 확률 0.03%), 같은 시간 박준호는 다른 일꾼 5명에서 57응답 정상 ⑤ 미확정: 좀비와 공존한 17:01/17:16/17:20 정상, 좀비 사후 18:14 동시 2개도 5.9초 지연 — 17:32 임계 초과 요인은 ECS 태스크 CPU·메모리 메트릭 없이는 확정 불가, 분당 경고율은 좀비 5.4 vs 피해 51.9라 좀비를 '가해자'로 단정하지 않음 ⑥ 라이브 모니터 선행 지표 3개(60분 초과 job·태스크 CPU·delay≥2초)와 미적용 처방(방치 job 자동 정리·무음 job 자가 종료·브라우저 자동 재배정) 안내.
좀비 job 슬롯 점유 — 같은 ECS 태스크 수업 job 지연 · AI 미발화 원인 분석 (2026-09-15)
9/15 17:32~18:12 아동 AI 미발화의 원인 분석. 테스트 계정 '테스트아리'의 방치 세션이 만든 job AJ_CLYSMmWdT876(pid 720)이 15:08:03~18:12:48로 3시간 5분 생존하며 워커 AW_eg5RnPhNXC2r의 슬롯 2개 중 1개를 점유 → 그 워커에 배정된 실수업 job이 예외 없이 '두 번째 동시 실행 job'이 되고 오디오 파이프라인이 실시간을 못 따라감(실효 처리율 1~6%) → LLM 텍스트는 늦게라도 도착하나 tts_node_first_audio_frame이 세션 종료 전 미도달 → "핑퐁이가 말을 안 해요". 확정된 것은 피해 범위(같은 @logStream 24 job 전수 일치, 손범 51회기 4룸 TTS 0건, 박준호 1회기는 다른 워커에서 57응답 정상, 좀비 종료 즉시 회복)와 소재(워커 × 시간 구간)까지. 촉발 요인은 미확정 — 17:20까지는 같은 동시 2개 조건에서 정상이었고, 좀비 사후에도 동시 2개면 5.9초 지연이 남으며, 분당 발생률로 정규화하면 좀비 5.4건/분 대 피해 job 51.9건/분으로 오히려 피해 job이 10배 밀려 '좀비의 CPU 독식'은 근거 없음. 기각한 가설 7종(워커 열화·이그레스 불량·Typecast 장애·커넥션 풀 오염·Consumer suspected silent 워밍업 프로브·no worker is available 노이즈·agent worker left the room) 포함. _ppi_agent_load가 job 개수만 보아 좀비 점유 중에도 load 0.495로 계속 재선택된 구조, delay 필드 정의(silero VAD 누적 적체), CloudWatch 판별 절차 5단계와 room 필터 함정, 처방 제안 5건(job 최대 수명 제한 P0 · 테스트 계정 격리 P1) 수록. 수정 미적용
readiness 타임아웃 자동 재시도 후 "듣기 ON" 미인식 — 의도 재예약 누락 원인 분석 및 수정
진행자 '듣기 ON' 상태에서 AI 준비(agent readiness) 10초 타임아웃이 한 번 발생하고 자동 재시도로 세션이 살아난 뒤, 시작 멘트와 진행자 타이핑 응답은 정상인데 아동 음성만 인식되지 않는 증상의 원인 분석. 원인 체인: use-step-transition.ts:111이 AI 스텝 진입 시 scheduleMicOnAfterFirstResponse({configuredIntent:true})로 듣기 의도를 예약 → 1차 startSession이 LiveKitAgentReadinessTimeoutError로 실패 → 실패 정리 await stopSession()(use-ai-session.ts:3877)이 isMicrophoneEnabledRef·pendingMicOnAfterFirstResponseRef·pendingConfiguredListeningRef를 모두 비움(2641·2647·2648행) → runAgentReadinessRetry는 같은 인자로 startSession만 재호출할 뿐 지워진 시작 조건을 다시 세우는 주체가 없음 → 2차 세션은 initialListeningIntent=false로 출발하고 첫 응답 후 applyDeferredListening()이 no-op → 아동 음성 업링크 계속 차단. 진행자 듣기 OFF→ON이면 그 자리에서 복구되는 것이 판별 근거. 수정은 runAgentReadinessRetry에 onBeforeRetryAttempt 콜백을 추가(폐기·비-readiness 실패 경로에서는 미호출)하고, handleStartSession이 시작 전 스냅샷한 의도로 재시도 직전에 예약을 다시 거는 것 — 수동 재시작 경로 restartCurrentSession이 이미 쓰던 스냅샷 패턴과 동일. "AI 준비 시간초과"가 무엇인지(방 접속은 성공, 에이전트 ready 신호 10초 미도달 · 미배정/기동지연/신호 미도달 3경우가 구분 안 됨)와 로그 4단계 검증 시퀀스, 강제 유발 방법 포함. 2·3차 라운드에서 두 건 추가 수정 — ① 복구 중 진행자가 듣기를 바꾸면 낡은 스냅샷이 이를 덮어쓰고(OFF의 경우 scheduleMicOnAfterFirstResponse가 취소 플래그까지 되돌림) 듣기 변경은 generation을 안 바꿔 폐기 검사에도 안 걸리는 문제를, listeningCommandSinceStartRef + 순수 함수 resolveRetryListeningIntent(최신 명령 우선, ON↔OFF 양방향)로 수정 ② stopSession이 generation을 올리지 않아 수업 종료 뒤에도 재시도가 새 방을 만들 수 있던 기존 공백을 stopGenerationRef(조기 반환보다 앞에서 증가) + getGeneration 합산으로 수정. 잔여 P2: 판정은 테스트했으나 배선은 훅 harness 부재로 미보호. 유닛테스트 21/21·tsc·prettier 통과, 실기기 재현 미검증. 구현 완료 · 미커밋
VP 모드 AI 응답 지연 — 엔드포인팅 타이머 리셋 원인 분석 (박지완 12회기 · 이유담 11회기)
2026-09-08 VP 모드(Soniox stt-rt-v5 · livekit_vad · 끼어들기 OFF) 두 수업에서 "AI 응답이 늦게 도착한다"는 신고의 원인 분석. 결론은 두 겹 — ① 아동이 말을 멈춘 뒤 앱이 endpointing 2500ms − VAD 600ms = 1.9초를 무조건 기다렸다가 턴을 확정하고 그 뒤에야 STT final → LLM → TTS가 붙어 모든 턴에 약 4초 기저 지연(박지완 마지막 멈춤→재생 중앙값 3.97초), ② 대기 중 VAD 재진입이 있으면 _resume_endpointing_turn이 타이머를 취소해 처음부터 다시 세는 구조라 박지완 32/46턴·이유담 30/83턴에서 리셋, 최대 9회 반복된 턴은 첫 멈춤→응답 25~36초. 한 턴 지연 해부 막대와 두 세션의 리셋 간트 시각화(박지완 20:10:22 턴 6회 리셋 29초, 이유담 20:56:00 턴 26.8초), 이유담 보류 구간 30턴 위치표(합계 229초)와 STT no-final fallback 8건 위치 포함. 두 세션 모두 TTS·아동 재생은 commit 전에 한 번도 시작되지 않음. 사이드 이펙트 추정: commit 전 생성된 응답 16건 중 10건이 아동의 뒷말을 반영하지 않은 채 같은 응답 ID로 재생. 미확정: 박지완 세션은 STT interim·final이 59/59턴 commit 이후에만 기록됐는데(이유담은 정상 스트리밍) 무음 치환·transcript gate·publish 배칭·stt_node·input gate를 코드로 배제했고 에이전트 서버 로그가 있어야 경계를 가를 수 있음. Codex(right pane) 리뷰 P1 2건·P2 5건 반영(commit→STT 인과 철회, "무관"→"주된 요인 아님", RMS 화자 미확정). 리셋은 특정 아동 문제가 아님(두 아동 모두 구절 사이 쉼 ≈1.2초, 재진입 뒤 2초 이상 실제 발화) → 원인은 끊어 말하는 발화 패턴과 2.5초 고정 엔드포인팅의 불일치. 개선 방향: 고정 문턱 축소 시뮬레이션(2.0초→10회·1.5초→42회 끼어들기)과 LiveKit dynamic endpointing 판정 기준(쉼 길이 EMA, 빨라지는 방향 아님, 앱 타이머와 불일치)·EOU 모델 정리. 재사용 판별법 5단계. 분석 완료 · 수정 미적용
iPad AI 음성 “밀림” 재현 — 저전력 모드 × WebAudio 렌더 부하 (디버그 하네스 실험)
PPI-1290-TEST 게스트 디버그 패널(mode=test)에 AudioContext 누적·AudioWorklet 렌더 부하·메인스레드 정지 하네스를 넣어 iPad 실기기에서 AI 음성 밀림 재현 조건을 좁힌 실험 기록. 정상 전원에서는 ctx 255개 누적, 렌더 콜백 40% 누락(227/s), 메인스레드 3초 정지 모두 음성 정상·영상만 끊김 → 개수·렌더기아·메인스레드 가설 기각. 충전 케이블 분리 + 저전력 모드 ON + 워크렛 150%에서만 밀림 재현, 저전력 단독은 정상 → 저전력은 증폭기, 원인은 저전력 × WebAudio 렌더 부하 결합. LiveKit 원격 오디오가 audioTrack.attach 엘리먼트 경로라 정상 전원에서 WebAudio·메인스레드와 독립인 구조 근거, 현장 부하 후보(loopback·입력 체인·analyser·ScriptProcessor 3곳), 하네스 함수별 코드 경로와 커밋 4건, 미확정 판별(저전력+하네스 OFF 실제 수업 흐름 + LIVEKIT_AUDIO_CHAIN health ratio)을 정리. 수정 미적용.
PPI-1276 그룹수업 "듣기 확인 중" 고착 — join-room 거부 재시도 부재 원인 분석 및 수정
그룹수업 진행자 화면의 "듣기" 버튼이 "확인 중"에 영구 고착된 문세찬 1회기(2026-09-05) 사례 분석. 원인 체인: 세션 시작 직후 게스트 연결 불안정 구간(transport_connect_error·transport close·LiveKit websocket code 1006)에서 join_rejected 발생 → use-mediasoup-socket.ts의 join-room 거부 분기가 재시도 없이 그냥 포기 → 다음 우연한 disconnect/reconnect가 올 때까지 room-handlers.ts의 JOIN_ROOM 성공 시 VOICE_CONTROL_UPDATED push가 안 감 → 게스트가 VOICE_CONTROL_APPLIED ack를 못 보냄 → resolveVoiceControlDisplayState가 hasGuestAppliedSnapshot=false인 동안 무조건 "checking" 표시. 수정은 기존 sessionReconcileRetry와 동일한 createSessionReconcileRetryController 패턴을 재사용한 bounded backoff 재시도(joinRejectionRetry, 1s/2s/3s 최대 3회)를 use-mediasoup-socket.ts에 추가, 판단 로직은 join-attempt-fence.ts의 nextJoinRejectionRetryDelay 순수 함수로 분리해 회귀 테스트 2건 추가. SFU가 왜 join-room을 거부했는지 근본 사유는 서버 로그 마스킹으로 미확정(UNKNOWN). tsc·유닛테스트 8/8·prettier·빌드 통과, 실기기 재현 검증은 미완. 구현 완료 · 미커밋
홍윤우 1회기 녹음 “지지직” — 단말 오디오 렌더 결손 원인 분석 (Codex 합의 루프 84%)
2026-08-31 20:33–20:51 홍윤우 1회기(room f6fbb622…_1, iPhone iOS 26.6 · Chrome 152=WKWebView) 녹음 mp3 4:30부터 아동 마이크와 AI 음성 양쪽에 지지직이 난 사건의 원인 분석. 결론은 아동 iPhone 안에서 오디오 렌더·재생이 실시간을 못 따라간 2~40 ms 결손으로, 게스트 Audio chain health의 AudioContext.currentTime/벽시계 ratio가 20:37:53(=mp3 4:29, 서버 mp3T0Ms로 확정)부터 0.99→0.81로 5분 계단식 악화 후 고정됐고(유효 190창 중 127창 <0.98), 활동 전환으로 새 AudioContext를 만들어도 첫 창부터 같은 결손이라 그래프가 아니라 단말 상태다. 파형 독립 확인 — 발화 사이 2~40 ms 근무음 갭이 4:25 이전 14개 → 이후 626개, 같은 세션 안에서 ratio ≥0.98 창 0.14 → <0.90 창 4.88 갭/발화초의 용량-반응(사분위·부트스트랩 CI·임계값 3세트 모두 유지), 갭 66%가 10 ms 미만(Opus 20 ms 패킷 손실로 불가), AI 발화 구간 안에서도 갭 2.4/초. 대조군 4세션(PC 2·iPhone 1 포함)은 전부 ratio 0.998~1.000으로 시그니처 없음. 기각: 패킷 손실 단독(스냅샷 0 + 갭 길이 + 활동1 정상), 서버 amix/스템 경계(경계 ±2초 갭 0~1, 156B 세그먼트 4건은 단명 raw-clone producer 정상 drop), 클리핑, 메인스레드 정지, suspend, SFU 전역 장애. 1차 원인(열/CPU 스로틀링 vs 오디오 세션 재구성)은 미확정 — 원준호 문서의 발열 반대 증거 병기. 장호준 9/1 지지직 사례는 시그니처가 없어 다른 기전(별건). Herdr 분할 pane에서 Codex(gpt-5.6) 리뷰어와 4라운드 합의 루프(68→76→79→84%, 목표 80%) — 리뷰어가 스크립트를 직접 실행해 전 수치 재계산 일치, 남은 partially 8건은 데이터셋에 없는 직접 증거(underrun 카운터·업링크 RTP·독립 스템·원본 OGG). 게스트 오디오 파이프라인 점검 추가 — WebAudio 그래프(컨텍스트 3개·네이티브 노드 ~20·워클릿 없음·20 ms 파라미터 자동화)는 결손을 만들 만큼 무겁지 않아 직접 원인 근거 없음, 단 60 fps 아바타 캔버스 합성·Opus 인코더 4개+H.264·비디오 디코드 3~4·활동마다 mediasoup 전송 전량 재생성(5회 확인, 트리거 미확정)은 단말 스로틀링에 간접 기여 가능, 경량화 후보 5종. 재사용 판별법 5종·재현 스크립트 3종(.omc/trace/crackle-0831)·계측 제안 포함. 분석 완료 · 수정 미적용
PPI-1267 좀비 녹음세션 반복 — 수정 설계·구현과 추론 과정
[갱신] 최종 수정은 원인 문서의 3종 제안이 아니라 훨씬 단순한 지점이었다 — 재접속(JOIN_ROOM)이 부르는 소유권 검증 API(validate-owner)에 수업 예정시각+1시간 hard-cut 체크를 추가하고, 검증 실패 시(수업시간 만료 포함) 게스트 소켓에 GUEST_FORCE_KICKED를 emit해 재접속 자체를 끊는다. 발견 계기는 "재접속시에도 수업시간 체크하냐"는 질문이었고, 추적 결과 시간 게이트는 최초 입장(/api/validate-lesson)에만 있고 socket 재접속 경로(JOIN_ROOM)엔 전혀 없었음을 확인. JOIN_ROOM 안의 두 소유권 재검증 지점(초입 + router 대기 중 재확인) 모두에 force-kick을 걸어 레이스 윈도우까지 커버. 처음 구현했던 "① 진행자 모니터 claim 게이트 녹음 억제, ② 발화 3시간 무신호 idle 타임아웃, ③ 좀비 stop 순서/discardIfEmpty"는 시간 게이트로 충분해 전부 롤백(각 항목의 결함과 롤백 근거를 별도 표로 기록). 최종 diff 4개 파일 +94/-5, 웹·소켓 신규 테스트 통과(관련 테스트 총 70건), 기존 socket 실패 17건은 develop과 동일한 무관 항목. 새로고침(F5)이 왜 이 경로만 정확히 재현하는지(세션스토리지 진입 플래그 우회, 세션 생성 API엔 시간체크 없음, socket connect→joinRoom)와 plannedStartTime 조정 기반 QA 재현 절차 포함. 커밋 0e962e33, PR #1060. 구현 완료 · PR 생성
석우주 8회기 아동 화면 미표시·AI 세션 실패 — iPad WebKit ICE 후보 미수집 원인 분석
수업 시작 후 약 4분 45초(2026-09-01 20:00:02~20:04:47, room 7f7291c4…_8) 동안 진행자 모니터에 아동 카메라가 뜨지 않고 AI 세션 생성이 매번 could not establish pc connection으로 실패한 사건의 원인 분석. 아동/모니터 세션로그 12,299건·ppi-livekit·ppi-socket-prod 세 로그를 교차해 층을 소거했다 — LiveKit은 같은 시간 참가자 170명 정상, SFU는 41개 룸 중 이 룸만 장기 connecting, 회선은 소켓·API·S3 25파일 다운로드가 전부 정상. 결정적으로 ICE 서버 없이 같은 페이지 안 PC 두 개를 잇는 기기 내부 loopback(webrtc-audio-loopback.ts:80)까지 타임아웃해 공유기·UDP 차단 가설을 배제했고, LiveKit 서버의 removing participant without connection 라인 publisherCandidates에 iPad에서 온 [remote] 후보가 0개(정상 접속 룸은 18개)라는 직접 증거로 iOS 26.6.1 WebKit(Chrome iOS = WKWebView) WebRTC 층의 ICE 후보 미수집으로 확정했다. WebRTC 4단계 중 ①PC/SDP ②시그널링은 6회 전부 성공, ③ICE/DTLS에서 매번 멈춤(15초 iOS ICE 타임아웃). 페이지 새로고침 2회·재진입 3회·진행자 kick 3회는 전부 무효였고 — 새로고침은 WebContent(JS) 층만 재시작하고 ICE를 담당하는 WebKit Networking 프로세스는 유지되기 때문 — 탭 이탈 후 back_forward 복귀 6번째 진입에서 200ms에 정상화됐다. 함정: 접속 직후 NETWORK_QUALITY 서버 도달 실패 WARN은 단일 단말의 HEAD /api/health 3초 타임아웃(reload 청크+S3 다운로드 포화 구간 추정)일 뿐 서버 장애 증거가 아니며 20:00:43에 해소됐다. 아동측 복구 절차(크롬 앱 완전 종료 > 앱 전환 후 복귀 > Wi-Fi 토글, 새로고침·kick 금지), 진행자 판별 신호, 웹 코드 대응 5종 제안(입장 직후 ICE 사전 프로브 > 전용 안내 토스트 > 모니터 상태 노출 > 빠른 실패 > iceGatheringState 로깅 — TURN/TCP 폴백은 무효), 세 출처 6단계 판별법 포함. 미확정: iceGatheringState 미로깅으로 후보 미수집 vs 패킷 미송신 구분 불가, 방아쇠(네트워크 전환) 로그 없음. 분석 완료 · 수정 미적용
좀비 녹음세션 반복 — 방치된 게스트 페이지 며칠 상주 원인 분석
ppi-socket-prod의 Failed to force-stop zombie session ERROR가 2.5시간 간격으로 반복된 원인 분석. 8/28~9/1 좀비 감지 27건 중 25건이 같은 방(8f078389…_37, 실사용 아동)이었고 진행자 접속은 0회 — 한 아동 기기가 37회기 게스트 페이지를 최소 4일 켜둔 채 방치한 무한 사이클이다. V2는 PIN self-entry라 승인 게이트가 없고, 재입장 fence는 같은 세대를 의도적으로 통과시키며, 수업 종료 처리가 없어 PPI-1250 중단회기 차단도 미적용, 게스트 유휴 타임아웃은 부재 — 네 게이트가 모두 비어 socket.io 재연결이 ~16분 주기로 JOIN_ROOM 재동기화+producer 재생성을 반복했다. 녹음은 입장만으로 자동 시작되지만 ffmpeg가 RTP 0패킷으로 20초 만에 타임아웃(stem 0kB)하고, 좀비 강제종료 시엔 같은 청소 사이클의 stale 파일 삭제가 먼저 실행돼 No valid recording segments로 throw — 즉 ERROR의 실체는 "건질 오디오가 없음"이고 세션 메모리 정리는 매번 정상 완료돼 실피해가 없다. 트리아지 절차(roomId→userId로 ppi-user-prod 조회 → Loki 좀비 이력으로 반복 여부 확정 → 양육자 채널톡 연락이 1차 대응)와 근본 개선 후보 3종(진행자 없는 방 녹음 자동시작 억제, 게스트 유휴 타임아웃, stale 삭제와 stop 순서 교정) 포함. 분석 완료 · 수정 미적용
LiveKit RPC 2초 response timeout — ACK pending 경고 발생 메커니즘 분석
RPC response received before ack 경고가 약 2초 간격으로 반복된 2026-08-27 lesson log 분석. 경고 17건은 두 브라우저 세션에서 발생했고, 세션 내 인접 간격 15개 중 13개가 2.004~2.016초로 PPI의 responseTimeout: 2000과 일치했다. 문구는 PPI가 아니라 livekit-client 2.19.2의 RpcClientManager가 출력하며, PPI 콘솔 수집기가 ctx: CONSOLE로 저장한다. SDK는 ACK를 7초 기다리고 상대에게 최소 8초 처리 제한을 전달하지만 로컬 응답 Promise는 2초에 먼저 종료될 수 있다. 그 finally에서 ACK가 pending이면 실제 response 수신 없이도 같은 경고가 출력되고 ACK 타이머가 제거된다. 따라서 확인된 것은 패킷 순서 역전이 아니라 타임아웃 종료 순서 역전이다. 2초는 SDK 규약상 허용되지만 원인 분류를 흐리며, 실제 요청 도달·ACK 지연·Agent 처리 지연은 request ID 종단 로그가 없어 미확정이다. 앱 수정은 없고, 후속으로 request ID별 발행→수신→ACK→처리→응답 계측, RPC별 p95/p99 기반 timeout 결정, 지연·중복 입력 상태 RPC의 최종 상태 일관성 검증을 제안한다.
원준호 45·46·47회기 “지지직” — 지터·오디오 클럭 언더런 계측 판별
아동 마이크와 AI 오디오 출력에 지지직이 잦았던 47회기(2026-08-27 20:29–20:49, room c14d884f…_47)를 같은 아이폰의 45회기·안드로이드 46회기와 대조해 원인을 큰 줄기로 가른 분석. 결론은 네트워크가 아니라 아동 단말의 오디오 렌더 언더런으로, 20:47:58–20:48:30 30초 동안 연속 6회 AudioContext 클럭이 실시간의 90~93%로 뒤처졌다(부족 288~506ms/5초). 이 체인은 ppi-livekit-input-audio-chain — 아동 마이크 입력 처리 그래프이고 여기서 나온 트랙이 LiveKit 에이전트와 진행자 릴레이 양쪽으로 나가므로, 언더런 하나가 아동 마이크 지지직을 직접 설명한다(네트워크를 타기 전 단말 안에서 이미 깨짐). 핵심 재사용 가치는 판별법 — Audio chain health 의 Δ벽시계와 Δ(AudioContext.currentTime)을 대조해 두 원인을 가른다: Δts≈5000인데 오디오 클럭만 느리면 렌더 언더런, 둘이 같이 늘어나면 메인스레드 정지·타이머 스로틀이다. 47회기는 벽시계 Δ가 전부 5002~5043ms로 정시여서 메인스레드는 멎지 않았다(콜백 지연 >200ms는 45와 동일하게 1회뿐). 집계 함정 2종 필수 — 체인 재생성마다 currentTime이 0부터 다시 시작하므로 기준을 끊지 않으면 12초짜리 가짜 지연이, 탭 백그라운드 전환(20:48:56 isVisible false) 구간을 정상 틱으로 세면 3,114ms 가짜 정지가 잡힌다. 지터는 열화가 맞으나(중앙값 4.5→10.5ms, 최대 13→35ms, 추세 +1.12ms/분 r=+0.67, 대조군은 r=+0.21/−0.07) 표본이 성기다 — consumer 생성 5초 뒤 1초 창에서만 찍혀 20분에 9점뿐이고 '가장 바쁜 순간' 편향이 있다. 12.6분 35ms/수신 34패킷은 1초 전 진행자측 video reset→seek→SEEK_REBUFFER와 아동 재입장 6초 뒤가 겹친 값이라 회선 열화 증거로 쓰면 과대 해석이며, 45회기도 같은 재입장을 했지만 4ms/51패킷으로 깨끗했다. 정정 2건 기록 — packetsLost는 델타가 아니라 누적값을 그대로 기록하는데(use-mediasoup-consumer.ts:101) consumer가 활동마다 새로 생겨 0에서 시작하므로 '손실 0'은 배제 근거가 아니라 각 consumer 첫 6초만의 미측정이다. 발열 가설은 반대 증거 3종으로 기각 — 같은 아이폰으로 9분 더 긴 45회기 언더런 0회, 지터 급등(12.6분)보다 언더런(18.2분)이 2분 늦음, 급등 시점에 진행자측 리버퍼라는 다른 설명 존재. 다만 iOS가 온도를 웹에 노출하지 않고 비디오 producer 통계(frameWidth·framesPerSecond·totalEncodeTime)를 로깅하지 않아 확인 수단 자체가 없다. AI audio element health는 HTMLAudioElement의 재생 위치라 렌더가 밀려도 시계가 그대로 흘러 언더런을 원리적으로 관측하지 못하며(세 회기 모두 오차 ±61ms 이내), 언더런 구간엔 60초 제한 때문에 로그가 아예 없다. 로깅 제안 3종(통계 주기 샘플링+손실 델타화 > 비디오 outbound 통계 > 클럭 부족 연속 초과 시 warn)과 세 회기 계측을 같은 시간축에 겹친 인터랙티브 차트 링크 포함. 🟡 직접 원인 확정 · 근본 원인 미확정 · 수정 미적용
송아인 24회기 AI발화·영상오디오 “지지직” — 아동 단말 출력 경로 오염 원인 분석
활동 스텝 AI 세션 중간부터 AI 발화와 영상 오디오 양쪽에 지지직 노이즈가 생기고 리프레시로는 회복되지 않아 브라우저 앱을 완전 종료해야 사라진 사건(2026-08-27 18:35–18:54, room bcd09fea…_24, 세션로그 11,029건)의 원인 분석. 신고 제목의 “Typecast 기계음”과 “iPad”는 둘 다 사실과 달랐다 — 이 세션은 클라이언트 TTS를 한 번도 쓰지 않았고(runtime livekit, tts_player_skipped 9건, AI 재생은 LiveKit remote track attach livekit-client-session.ts:1495), 단말은 비-iOS Chromium이다(audioSessionState null · outputLatency가 숫자로 찍힘 · outputDeviceCount 1 · 카메라 라벨 “camera 1, facing front” 4개 독립 근거). 그 결과 iOS AVAudioSession·WebRTC loopback·256kbps CBR Opus·공유 AudioContext 샘플레이트 가설이 전부 기각된다 — isIosFamilyDevice 게이팅(lib/utils.ts:22-25, guest-layout-content.tsx:86-89)으로 이 단말에서는 loopback이 애초에 생성되지 않으며, 로그의 loopback 문자열 0건은 관측 공백이 아니라 미실행의 결과다. 남는 유력 가설은 아동 단말 브라우저/OS 공용 출력 스트림(캡처·AEC와 결합된 통신 오디오 경로) 오염으로, AI 발화와 영상 오디오가 동시에 깨지고 탭 리로드로 회복되지 않는 세 제약을 한꺼번에 설명한다. 직접 증거는 outputLatency가 마이크 캡처 상태에 결합되어 0.06→0.008→(mic mute 직후)0.062로 재구성된다는 실측과, 18분 세션에서 LiveKit room·PC·AudioWorklet이 9회 전량 재생성되며 매번 AEC가 재초기화된다는 로그(Defer mic: applied after AEC stabilization delay 8건)다. “EC가 꺼져서”라는 가설은 기각 — iOS에서 echoCancellation이 voice-processing(AEC+NS+AGC) 전체 스위치라는 전제는 맞지만 이 단말은 iOS가 아니고, 런타임에 EC를 끄는 코드 경로도 없다(전 경로 true 고정, applyConstraints는 화면공유 비디오 전용). 다만 직관의 핵심은 유효해서 범인 후보는 “꺼짐”이 아니라 스텝마다 반복되는 “재개설”이다. “진행자도 들었다 → 업링크가 오염됐다”도 전제 오류로, 진행자는 같은 영상을 자기 단말에서 직접 재생 중이며(volume 1 muted false) 영상 오디오는 구조적으로 업링크되지 않는다 — 진행자 기본 출력이 다중 출력 기기(Aggregate)+BlackHole 2ch라 자체 잡음 가능성도 남는다. 서버측은 전수 점검 결과 이상 없음 — LiveKit SFU 로그(방 10개, teardown warn만), ppi-socket 이 방 error 0건, 서버 CPU 정상(livekit-prod 4.9%), agent TTS 텔레메트리 50턴 전부 completed/errorClass None(단 eof elapsedMs 중앙값 757→943ms 완만한 증가). Dropping unreadable/corrupt stem segment 4건은 같은 날 다른 방 10~18건 대비 오히려 적어 기저 패턴이며, agent 합성 PCM 파형 품질만 미확인이다(Loki에 ppi-livekit-agent 라벨 자체가 없음). 녹음 합본 4건(신고 1+대조 3) 실측에서 신고 세션의 클리핑 밀도가 5.32/초로 대조군 1.74/1.18/0.03 대비 뚜렷이 높고 전체 RMS도 4~5dB 높지만, amix 이후라 AI/마이크 분리가 안 돼 단독 증거는 아니다. 즉시 판별 3단(ai-audio stem 청취 > 진행자 출력을 내장 스피커로 A/B > 재발 시 “브라우저 밖 다른 앱 소리도 지지직인가” 확인)과 앱측 조치(단말 지문·주기 AUDIO_PROBE 로깅 > NetEQ concealedSamples 등 출력단 판별 지표 > 스텝마다의 room·PC 전량 재생성 축소)를 정리했고, JS 미디어 런타임 강제 재생성은 세션 중 9회 자연 재생성이 이미 실패했으므로 권고하지 않는다. 🟡 원인 미확정 · 유력 가설 1건 · 수정 미적용
이윤준 8회기 종료멘트 선발화 자동전환 실패 — 스텝 미매칭 원인 분석
핑퐁이가 스텝1(5명 친구중에 고르기)의 종료멘트를 아직 스텝0(대화 2)에 있을 때 미리 발화해 자동전환이 안 된 사건(2026-08-25 20:12, room 30806c4c…_8, 아동 로그 15,028건). 자동전환 판정은 오디오 종료 시점의 현재 스텝 phrases만 검사하고 실패하면 응답을 즉시 폐기하므로, 선발화 문장은 0/3 미매칭으로 버려졌다 — LiveKit agent_response_completed 는 20:12:09.723 에 정상 도착했고 checkPhrase 정규화도 정상(66발화 전수 시뮬레이션 오탐 0)이어서 이벤트 누락이 아닌 순수 미매칭이다. 선발화 트리거는 아동 2분 무응답 뒤 진행자가 보낸 무응답 필러 "음..."(responseInstructions 없이 전송)로, 세션 instruction 이 활동 단위 1개뿐이라 모델이 스텝을 인지하지 못한 채 내용 없는 입력을 마무리 신호로 해석한 것으로 추정된다. 복구는 자연 회복이 아니라 진행자 수동 전환 + 프리셋 "- 다음으로 넘어가자" 입력이었고 체감 지연은 약 42초, 더 큰 손실은 아동이 스텝0 과제 지시를 끝까지 듣지 못한 것. 권고는 필러·스텝 메시지에 기존 responseInstructions 경로로 스텝 유지 지시를 동봉(신규 채널 0) > 다음 스텝 문구 감지 시 자동 전환이 아니라 진행자 확인 배너 > 미매칭 폐기 지점 로그 추가이며, 자동 look-ahead 는 절감 3.5초 대비 아동 활동 무음 스킵·jumpConfig 오분기 위험이 커 반대. 같은 증상의 인접 경로 3종(스텝 미매칭 / LiveKit 완료 이벤트 누락 / 끼어들기 폐기) 판별 시그니처 포함. 분석 완료 · 수정 미적용
LiveKit agent readiness 타임아웃 — 워커 디스패치 즉시 포기 원인 분석
LIVEKIT_CLIENT_SESSION|LiveKit agent readiness wait failed 로 AI 세션이 시작되지 못한 사건(2026-08-25 3건, 08-24 2건 재발)의 원인 분석. 전체 워커 풀에는 빈 슬롯이 11개 있었지만 그 job 을 처리한 LiveKit 노드에 등록된 워커 11개가 전부 꽉 차 있었다 — prod 는 노드 3대로 운영되고 워커 분포가 11/10/3 으로 고르지 않았으며, 여유 워커 8개는 모두 다른 노드에 있었다. "서버가 AVAILABLE 을 11개나 아는데 왜 부족한가"는 주체를 하나로 뭉쳐 생기는 착시로, 11개는 클러스터 전체 합계이고 판단 주체는 노드 하나다 — 46-118 의 AgentHandler 가 순회하는 namespaceWorkers 에는 자기 워커 11개만 있고 그중 WS_AVAILABLE 은 stale 3개뿐이라 셋 다 거절하자 availableSum==0 이 됐다. 숫자도 맞아떨어진다: 전체 11 = 진짜 여유 8(다른 노드) + stale 3(그 노드). 그 노드에서 아직 WS_AVAILABLE 로 보고돼 있던 3개(막 채워져 status 미갱신)만 후보가 됐고 셋 다 실제로는 꽉 차서 거절하자 후보가 고갈돼 job 이 폐기됐으며, 클라이언트는 통보를 못 받고 10초 뒤 스스로 타임아웃했다. prod 서버 버전은 기동 로그의 version 필드로 v1.13.2 확정이며(해당 태그 소스의 로그 라인 402/412/428 이 prod caller 와 정확히 일치, master 는 415/425/441), 그 버전의 JobRequest 에 재시도 상한이 없음과 namespaceWorkers 가 노드 로컬 맵이라 후보 선정이 자기 노드 워커만 순회한다는 것을 소스로 확정했다 — "다른 노드에 여유가 있어도 볼 수 없다"가 이 사건의 코드 근거다. 마른 노드가 job 을 가져온 이유도 JobRequestAffinity 가 WS_AVAILABLE 워커의 여유 합으로 계산되기 때문으로, stale status 가 두 경로로 작용한다(마른 노드를 끌어들이고 + 그 안에서 헛후보가 된다). 조사 과정에서 결론이 두 번 바뀐 문서다 — 초판 "서버가 후보 1~3개만 시도하고 포기"(재시도 상한 오해; agentservice.go JobRequest 는 attempted 맵으로 제외하며 후보가 바닥날 때까지 도는 상한 없는 루프라 3개에서 멈춘 것은 후보 고갈이었다), 2판 "status 2.5초 지연으로 여유 워커가 후보에서 빠짐"(worker.py 의 _update_worker_status 호출부가 draining 과 2.5초 주기 태스크 둘뿐이라는 코드 근거였으나, status 전환 로그 405건을 전수 재구성하니 서버가 아는 AVAILABLE 이 11개여서 지연만으로 설명되지 않았다), 현재 판이 노드 단위 포화다. 검증의 핵심은 워커별 교차검증 — 슬롯 점유(서버 로그)와 status 보고(에이전트 로그)가 21/24 일치하고 불일치 3개가 정확히 서버가 시도해 거절당한 그 3개(슬롯 2/2 인데 status AVAIL)라, stale AVAILABLE 이 후보로 뽑힌 것까지 팩트로 확정된다. status 전환은 prod 로그에 load/threshold 값과 함께 찍힌다(2잡 0.99, 1잡 0.495 — agent.py:299-325 의 active_jobs/2 × 0.99 그대로). 세 가설 판정: 세션수 급증 아님(전체 동시 점유 최대 44/48), 사후 점유 20초는 기여 요인(각 노드의 여유 워커 수를 줄임, 전체의 14.9%, 20:17:06 기준 여유 워커 10→15 로 늘어남), 직접 원인은 노드 포화 + 후보 고갈 시 재시도 없음 + 실패 통보 없음. 사후 점유의 정체도 empty_timeout 이 아니라 departure_timeout 으로 prod 로그의 reason 필드가 확정했다(rtc/room.go:804 closing idle room, 4분 창 72건 전부, empty timeout 0건, config.go 기본값 300초/20초, time.Now().Unix() 초 단위 연산이라 실측 min 19.6/max 20.7 편차까지 일치). 20초 유예는 575개 방에서 한 번도 쓰이지 않았고(25초 초과 0건, 한 방에 job 2회 배정 0/571 — launchRoomAgents 가 방 생성 시 1회만 호출되므로 재입장해도 새 에이전트가 안 붙는다), close_on_disconnect 로 세션은 즉시 닫히므로 돌아와도 방 껍데기만 남는다. 조사 함정 5종과 Grafana 조회 경로(viewer 는 Explore 권한이 없어 ppilogbrowser-app URL 파라미터를 쓸 것) 포함. 개선 우선순위는 노드 간 워커 분산 점검 > 사후 점유 20초 제거(agent.py:1709 attach_session_close_job_shutdown 신설 + 테스트 10건, 로컬 적용) — 후자는 순단으로 오발동하지 않는다(세션 종료 사유가 CLIENT_INITIATED/ROOM_DELETED/USER_REJECTED 셋뿐이라 PEER_CONNECTION_DISCONNECTED 는 해당 없음) > 워커당 슬롯 2→3 > 클라이언트 재시도(유일한 확실한 안전망 — livekit-readiness-retry.ts 순수 함수 신설 + 테스트 9건, use-ai-session.ts:4658 배선, 로컬 적용). 재시도는 readiness 타임아웃에만 1회 발동하고 800ms 뒤 startSession 을 재호출하는데, createVoiceSessionId 가 매번 새 UUID 를 반환해 새 방이 만들어지므로 노드·워커가 재선택된다(같은 방으로는 launchRoomAgents 가 방 생성 시 1회만 호출돼 새 job 이 안 나간다). 대기 중 스텝이 전환되면 generation 가드로 버리고, readiness_retry_scheduled/_succeeded/_exhausted/_abandoned 로그로 복구율을 측정할 수 있다. 타임아웃 10초는 유지해 최악 21초이며, 실측 정상 readiness 최대 1.62초(n=29) 대비 단축 여지는 별도 판단으로 남겼다이며, status 주기 단축은 affinity 경로가 확인되면서 폐기 대신 "노드 분산 점검 다음 후보"로 되살렸고, load_threshold 1.0 안은 폐기했다. dev 검증 항목 5종(job ended 간격 20초→1초 미만, requesting job shutdown after session close 로그, readiness_retry_* 로그, 재시도 시 화면 끊김, 순단 오발동)과 효과 측정 기준선(no worker available 57건/일, 세션 실패 3건/일 — ①은 앞 숫자를 ②는 뒤 숫자를 줄인다)도 문서화했다. 🟠 잠정 결론(워커→노드 매핑은 로그 다수결 추정, 노드 선택 결과는 로그 미기록) · 수정 2건 PR #1027(브랜치 PPI-1237, 커밋 314fb05b) · 노드 불균형은 미해결
공통_룰 프롬프트 변수가 활동 지침을 무효화한 원인과 v3 수정
활동 프롬프트에 #{공통_룰}을 넣으면 받기(되짚기)·"하나 둘 셋" 고정 멘트·STEP 진행이 통째로 무시되던 문제. 원인은 토큰 한도(16,969/32,768 tok)가 아니라 공통 변수가 활동 규칙 뒤에서 "다른 모든 규칙보다 우선"을 선언하고 활동이 시키는 동작을 금지한 것 + 잼민백과사전 4,788 tok가 규칙 밀도를 희석해 MUST/NEVER 65개가 상호 모순된 것. 문제 문장 5줄만 고친 최소수정은 실패, 분량 절반·충돌 제거·우선순위 명시한 재작성(8,635 tok/22개)은 통과. 공통_룰 v3(3,136→1,872 tok) 수정 내역과 재발 방지 규칙 5가지 포함.
PPI-1233 영상 스텝 중 AI 발화 출력 — LiveKit response_created 미연결 원인 분석 및 수정
보상전환영상 재생 4.2초 지점부터 약 6초간 티키 발화가 영상 위로 겹쳐 나온 사례(변재하 10회기, video_tiki_bridge_end_event_0)의 원인을 코드까지 확정하고 수정. 스텝 전환·마이크 뮤트·manual_interrupt 3회는 모두 정상 동작했고, 뚫린 곳은 '차단 상태에서 새로 시작된 agent 응답을 취소하는 경로' 하나였다. 발동 조건은 아동이 티키 마지막 발화 꼬리에 겹쳐 말해 auto-finish 전환 시점에 아동 턴이 pending으로 남는 것 — interrupt는 그 순간 진행 중인 발화만 끊고, 살아남은 pending 턴이 2.5초 뒤 만드는 새 응답은 못 막는다(영상 재생 +2.57s agent_response_started → +3.30s tts_provider_first_pcm → +4.16~10.04s 발화 3구간). 근본 원인은 비-AI 스텝에서 새 응답을 즉시 취소하는 방어가 response_created 이벤트에 걸려 있는데, 그 이벤트가 OpenAI Realtime의 response.created만 매핑돼 있고 liveKitDataToVoiceSessionEvent에 agent_response_started 분기가 없어 null로 버려진 것 — 이벤트 타입이 isAgentVoiceEventType 목록에는 있어 신뢰 검사는 통과하므로 조용히 사라진다. 신규 정책이 아니라 런타임 간 동작 불일치로, use-step-transition.ts:186 주석이 정책과 PPI-907 revert 사유('cancelResponse만으로는 발화 중 진입 시 미래 response를 못 막아 영상/이미지 위로 AI 발화가 흐르는 회귀')를 이미 적어 두었다. 런타임 3종 비교 — Realtime은 response.cancel + output_audio_buffer.clear로 생성을 죽이고, TTS 모드는 오디오를 transcript delta로 클라이언트에서 합성하므로 전사 가드가 곧 오디오 가드였으나, LiveKit은 agent 서버측 TTS가 published track으로 도착해 전사를 버려도 소리가 그대로 난다. 수정은 매퍼에 분기 신설(agent_response_id가 없어도 이벤트를 버리지 않는 것이 핵심 — 차단 판정은 id에 의존하지 않으므로 버리면 다시 뚫린다) + 그로 인해 새로 생기는 함정인 pending stuck 방어(LiveKit은 response_done을 발행하지 않아 setIsResponsePending(true)를 켜면 끌 이벤트가 없다 → shouldTrackResponsePending 순수 헬퍼로 realtime 계열 한정) + 차단 로그에 source 필드. 판별법 4종 포함 — 차단 성공 여부는 'Cancelling response.created during non-AI step block'의 유무이며 수십 건 찍히는 'transcript … ignored during video transition'은 화면 차단만 뜻해 근거가 되지 않는다(이번 세션 전자 0건), 오디오 실제 생성은 tts_provider_first_pcm 도달, 진행자·녹음 유입은 ai-audio producer 생존 구간 겹침, 취소 타이밍 여유는 agent_response_started→first_pcm 실측 730ms. 부수 발견(미확정) — agent가 turn-1을 user_turn_dropped로 떨궜는데도 350ms 뒤 response-2가 시작됐고 on_user_turn_completed_trace가 드롭 마커보다 먼저 찍혀 경합이 의심되나 클라이언트 로그에 publish_decision이 없어 확정 못 함. 남은 범위 — 생성 자체를 막는 agent.setResponseBlocked RPC(StopResponse 경로)와 릴레이 트랙 게이팅은 별건. 🔵 원인 확정 · 수정 로컬 적용(브랜치 PPI-1233, 미커밋) — 실기기 대화 검증 필요, 비-AI 스텝에서 진행자 프리셋 발화도 함께 취소되는 동작 변화 확인 필요
AI 미발화 원인 확정 — 빈 전사 캐시 키가 앞 턴 판정을 재생해 superseded 오판
아동 턴이 정상 커밋되고 전사까지 생성됐는데 핑퐁이가 16.7초간 무응답이었던 사례(70회기 10:50:28)의 원인을 코드까지 확정. 세션 로그에서 아동 발화 37턴을 전수 재구성해 응답 없는 턴 5건을 특정하고(정상 커밋→응답은 예외 없이 0.37~1.08초), 메커니즘 3종으로 갈랐다 — A 재발화 조기 drop 3건(pending 대기창 1.90초 안에 다시 말하면 대기 턴 폐기, 오디오가 새 턴으로 승계되지 않아 user_stt_segment조차 없이 내용 전량 유실, 9.9초 발화가 통째로 사라진 건 포함), B AI 발화 중 커밋 1건(allow_interruptions=false 구간이라 훅 미실행, auto-finish 스텝 전환으로 묻힘 — 뒤따른 응답은 다음 세션 오프닝 멘트로 오인 주의), C 캐시 키 오염 1건. C의 근본 원인은 input_gate.resolve_completion이 판정 결과를 정규화된 전사 문자열을 키로 캐시하는데(:227) 훅이 보는 전사가 항상 비어 있어(transcript_length 0, user_stt_segment가 커밋보다 0.7초 늦게 도착) 모든 턴이 키 빈문자열을 공유하는 것 — 직전 거부 턴(turn-3)의 결과를 pop("")으로 히트해(:184) last_resolved_turn_id를 turn-3에 고착시키고 turn-4는 판정조차 되지 않는다. turn-4가 더 최신이므로 agent.py:1583 has_newer_turn_after("turn-3")가 참 → superseded_by_new_speech → :1632 StopResponse()로 응답 생성 취소. 방어선 둘이 함께 무력화된다(remember_final_transcript가 빈 전사를 등록하지 않아 정확 매칭 경로 영구 차단 + 빈 전사 가드가 :212 가장 깊은 else에만 있어 캐시 히트 경로가 그 위에서 return). 발동 조건은 같은 세션에서 user_turn_dropped가 한 번이라도 나면 다음 아동 발화가 응답을 잃는 것으로, 훅 33회 중 조건 성립 2회 / 미발화 2회로 2/2 적중. 함정 — user_turn_dropped 5건 중 2건은 응답이 정상 발화되며(pending→종료 1.90초 만료형), 이벤트 이름만 보고 지목하면 22초 뒤의 진짜 미발화를 놓친다. 재사용 판별법(턴 원장 구성 시 voiceSessionId 경계 준수, pending 소요 1.90초 기준, resolved turn_id ≠ last_observed_turn_id면 캐시 오염 확정)과 prod agent 로그 조회 레시피(Grafana /api/ds/query → CloudWatch Logs Insights /ppi/livekit/prod/agent, strcontains 컴파일 실패 / DescribeLogGroups 거부되지만 StartQuery는 통과 / event_id dedupe 필요) 포함. LiveKit 서버 로그 warn·error 0건으로 전송 경로 배제. 사슬은 실제 DeferredInputGate로 로컬 재현해 prod 로그값과 4턴 전부 일치 확인, 기존 동작 검증 테스트 2건 추가(test_input_gate 23건 통과 — 전체 스위트 3건 실패는 develop 동일 기존 항목). 실기기 재현 절차도 포함 — 거부 턴을 만든 실제 조건은 발화 종료 직후 desired/effective 입력이 True→False→True로 0.5초 플랩하는 창(10:49:55.613~56.422)에서 아동이 말을 시작한 것이고, 모니터 듣기 OFF 토글로 그 창을 조작 가능한 길이로 늘려 유도할 수 있다. 듣기 OFF가 마이크 트랙을 끄지 않음을 코드로 확인(livekit-client-session.ts:1177 createLiveKitAgentOnlyInputStateApplier 주석, 에이전트는 turn_guard 플래그만 변경)해 VAD가 계속 돌아 거부 턴이 생기는 전제를 확정. 7단계 조작표·확진 지표(resolved turn_id ≠ last_observed_turn_id)·실패 함정 4종(듣기 OFF 전 발화 시작 / 1.9초 못 기다림 → 메커니즘 A / 핑퐁이 발화 중 진행 → 메커니즘 B) 포함. 🔵 OPEN — 수정 미적용(실기기 대화 검증 필요), 메커니즘 A는 별개 사안
PPI-1227 LiveKit agent AI 미발화·기계음 3계층 진단 로그 추가 (Typecast 회신 반영)
livekit 런타임에서 "AI 발화 이벤트는 났는데 소리가 안 났다"를 판별할 근거가 없던 관측 공백을 메운다. 세션 로그 1건(15,759줄)을 실측해 세 가지 공백을 확인했다 — role이 전부 guest라 agent 로그가 아예 없고, 클라이언트가 받은 agent 이벤트는 eventType만 남기고 boundary·outcome·elapsed_ms를 버리며(LiveKit data received 4,396건), 기존 terminal boundary는 단계 도달 여부만 담아 1프레임 후 끊긴 발화와 정상 발화가 같은 로그를 남겼다. provider(typecast_tts_synthesis / _empty_audio / _failed — pcm_bytes·audio_ms·first_pcm_ms·format_source) → tts_node(tts_node_audio_output / _empty / _incomplete — frame_count·audio_ms·sample_rate) → 룸·트랙(_audio_output_snapshot — publication sid/muted, room_connection_state) 3계층에 수치를 남겨 끊긴 지점을 지목하고, 클라이언트 payload 유실도 함께 막았다. AI 미발화 7행·기계음 3단계 판별 매트릭스와 아동측 로그 7종(Audio chain health의 lastLevelDb, Audio output suspected silent 등) 대조표 포함. 부수 발견 — 오디오 완전 0은 LiveKit AudioEmitter가 이미 APIError로 올리므로 실제로 안 보였던 건 부분 송출이었다. 함정 — LIVEKIT_TEST_LOG_FORMAT이 json이 아니면 basicConfig가 record.payload를 읽지 않아 수치가 조용히 사라지고, 배포 액션이 이 변수를 갱신하지 않아 코드로 확인할 수 없다(PayloadTextFormatter로 양쪽 모드 대응). Typecast 공식 회신 5개 항목 요약도 담았다 — RIFF/data size 0xFFFFFFFF는 Chrome·Safari 모두 정상 처리(비트 단위 동일)로 지지직 원인 배제, 32k→48k 리샘플도 배제, 피치·음색 변동은 seed 미지정에 따른 의도된 동작으로 확정, 웹 전체 버퍼링 경로는 일반 API 권장(agent는 실제 스트리밍이라 대상 아님). WAV 끝 길이 검증 4겹(RIFF size·data 선언 길이·16bit 샘플 정렬·fmt 스펙)도 정리 — 스트리밍 시절 RIFF size가 항상 0xFFFFFFFF여서 잘림 검사 첫 조건이 영구히 false였고 응답이 끊겨 와도 통과했다. sampleRate를 32000 고정으로 검증하지 않은 이유(OpenAI TTS가 같은 검증기를 타서 전부 502가 된다)와 리뷰에서 잡힌 trailing chunk 오인(byteLength−dataOffset로 계산하면 data 뒤 LIST/JUNK까지 오디오로 세어 정상 WAV를 502로 거부 → 선언된 data size 우선 사용) 포함. 🟡 지지직 자체는 이 PR로 해결되지 않으며 별도 티켓 필요 — Loki 라벨 키와 prod LIVEKIT_TEST_LOG_FORMAT 값은 미검증
김민호 17회기 AI 발화 토막 — TTS 공급 붕괴와 재생 버퍼 부재 원인 분석
"AI가 '오, 오!'만 반복한다"로 신고된 사건이 사실은 완전한 한 문장이 재생 도중 20조각으로 토막난 것이었다. 텍스트 생성(1회·완전·반복 없음)과 발화 시작 시각(1.52초 / 중앙값 1.57초)은 모두 정상이고, 무너진 것은 재생 도중의 음성 공급뿐이다 — 무음 12회·합계 5.2초로 세션 60개 응답 중 1위(2위 3.6초, 중앙값 0.8초). 반복 생성은 발화 속도 5.59 자/초가 같은 할아버지 캐릭터의 다른 응답(5.36/4.95/5.41)과 동일해 배제되고, 파형 상관 0.15(조각이 서로 다름)도 같은 방향이다. 자기끊기 루프(agent_response_started 1회·agent_state_changed 2회·취소 전 interrupt 0건), 에코 유입(입력 게이트 40회 전부 닫힘·localSpeaking 27회 전부 false), 네트워크·SFU(해당 룸 LiveKit 서버 로그 0건, 같은 창의 다른 룸은 정상 기록), 아동 기기·재생(currentTimeAdvanced true), 영어 가드(연속 2회로 임계 미달)를 모두 배제했다. 문장 분할 방식도 원인이 아니다 — blingfire 토크나이저로 확인한 실제 분할은 2조각(58자+11자)이라 요청 사이 틈은 최대 1번이고, 나머지 11번은 요청 1 하나를 받는 도중에 발생했다. 구조적 지점은 typecast_tts.py:252가 4KB 청크를 도착 즉시 재생으로 넘기는 것 — 요청 1은 133청크가 초당 16개 페이스로 계속 와야 하는데 완충 여유가 0이라 한 번 늦을 때마다 한 번 끊긴다. 공급이 밀린 이유는 미확정으로, Typecast 전송 지연·에이전트↔업체 네트워크·에이전트 처리 지연 셋을 가를 수 없다(청크 루프에 로그 0개, 중간 132청크 도착 기록 없음). 보고 지연 +1.57초(세션 최대, 중앙값 −0.28초, 무음 총량과 r=0.59)는 에이전트 정체를 시사하는 정황이나 데이터 채널 지연을 배제하지 못한다. 재사용할 판별법 3종 포함 — 발화 속도로 반복 생성 배제, 무음 총량으로 이상 응답 특정, boundary 6개의 정체를 59개 응답 통계('첫 음성보다 먼저 오는 비율' 98/93/71/0%)로 역산. 아동 브라우저 시계 −3.76초 보정법과 언어가드 MFCC 버퍼가 토막마다 폐기되어 마지막 309ms만 남는 부수 발견도 기록. 🟡 원인 미확정 — 수정 미적용, 진행자 '말하기 멈춤' 1회로 종료
박승율 1회기 종료멘트 자동전환 실패 — LiveKit 완료 이벤트 누락 원인 분석
종료 멘트는 화면에서 매칭·하이라이트됐으나 자동전환이 발생하지 않은 1회기 사례. 텍스트 검사와 실제 자동전환 사이의 완료 이벤트 게이트를 시간축으로 분리했고, 문제 응답은 agent_response_terminal_boundary만 수신하고 agent_response_completed가 누락돼 assistant_audio_stopped·Auto-finish triggered·진행자 자동전환 전송이 모두 발생하지 않았음을 동일 세션 정상 사례와 대조했다. 원인은 LiveKit 완료 이벤트 누락 경계까지 확정, 에이전트 측 누락 원인은 추가 관측 필요. 수정 미적용.
LiveKit 방 전환 레이스 — 아동 마이크·AI 오디오 무음 송출 원인 분석
아동 마이크가 진행자 모니터에 전달되지 않은 세션 2건(Android Chrome)의 원인 분석. SFU 연결 문제가 아니라 오디오 소스가 무음이었고, 마이크만이 아니라 같은 방에서 AI 수신 오디오도 함께 죽어 있었다 — 상향은 에이전트 user_turn 0건(최대 58초)·mediasoup packetsSent 0, 하향은 재생 시계 정지(currentTime 0.01, advanced false)·AI 오디오 캡처 0. 그 사이 DataChannel은 살아 있어 agent_response_started는 계속 수신됐다. 발생 조건은 AI 활동이 연속으로 이어져 방 전환이 2초 안에 끝난 경우로 10/10 재현되며, 8초 이상 벌어진 8건은 0/8이다(단 그중 5~6건은 kick+리로드라 순수 비교군은 영상 스텝 3건). 관측 전부를 하나로 설명하는 지점은 LiveKit Room이 송신 처리 그래프와 수신 재생에 공유하는 AudioContext이며, acquireAudioContext가 suspended를 200ms만 기다리고 통과하는 것이 유력 후보. raw 트랙 사망·게이트 감쇠·컨텍스트 누적·역인과·clone 무음·audio element cleanup 레이스를 배제했고, 최초 지목했던 disconnect 재진입 가드 레이스도 로그 대조 결과 미발생으로 정정(abortHandler가 연결 성공 시 finally에서 제거됨). 재발 판별법(전환 간격 2초 + user_turn 0건 + currentTimeAdvanced false)과 PPI-1196 계측 내역 포함. 🟡 원인 미확정 — 계측 적용, 실기기 확인 대기. 진행자 수동 kick 6회 발생
PPI-1192 — iPad LiveKit AI 발화 무음과 setSinkId 사용자 제스처 수정
인트로 영상 오디오와 아동 마이크 relay는 정상이지만 AI 활동 전환 뒤 LiveKit 발화만 무음이 된 iPad 사례. Agent 트랙은 구독·attach되고 재생 시간도 전진해 마지막 출력 단계가 유력했으며, TrackSubscribed가 사용자 제스처 밖에서 선택 sink를 재적용하던 결함을 제거했다. sink 선택은 warmUpAudio와 pointer/touch 제스처에서만 동기 시작하고, devicechange는 상태만 무효화한 뒤 다음 제스처에서 재적용한다. 요청 경합·실패 재시도·공유 AudioContext 보존과 회귀 테스트 29건을 포함하며, 실기기 최종 청취 검증은 남아 있다.
예비 활동 19분 스킵 오작동 — 누적 시간 4분 누락 원인 분석 및 해결
누적 19분 이상이면 예비 콘텐츠를 건너뛰어야 하는데(PPI-580), 19분을 넘긴 수업에서 예비 활동 2개가 그대로 재생된 사례(이소헌 4회기, 2026-07-27)의 원인 확정. 판정부는 정상이고 판정에 들어간 누적 시간이 틀렸다 — 레슨 세션 레코드가 '수업 시작'이 아니라 '첫 AI 대화 스텝이 붙는 순간'에 생성되는 탓에, 오프닝 영상 3스텝 3분 59초가 누적에서 통째로 사라진다. 레코드 도착 시 fallback 경과분이 폐기되고 단조 클램프가 손실을 '동결'로 가려 정상처럼 보인 점, Loki(첫 스텝 시각 vs sessionId epoch)와 LogRocket(누적 동결 구간·SPARE_CONTENT_UTILS reason)의 판별법, DB totalDuration·입장 1시간 한도까지 4분 과소 기록되는 동반 영향, 예비 그룹 base name 판정이 운영 타이틀 포맷에서 무의미해진 부수 발견 포함. 해결은 createSession 트리거를 confirmReady로 이전(근본, 서버 enteredAt까지 교정) + fallback offset 보존(방어). 🟡 원인 확정·수정 미적용
PPI-1167 — revert 후 재랜딩과 3연쇄 수정 (듣기 복원 실패 · presence race)
진행자 부재 시 AI 듣기 자동 ON(#935)이 머지 후 revert(#940)되고 재랜딩(#941)되기까지의 사건 정리. revert 사유는 새 기능이 아니라 기존 동작 파손 — 진행자 '듣기' 의도(configured)가 page-session ref(정본)와 ai-session ref(세션마다 리셋되는 사본) 두 곳에 존재하는데, 1167이 예약 마이크 ON의 판정을 사본 쪽으로 옮겨 활동 전환 후 복원이 no-op이 됐다. 사본이 false로 리셋되는 3경로(stopSession·비-AI 스텝 mute·비-AI 스텝 중 진행자 ON 차단), 세션 시작 트랙이 이미 닫혀 있어 예약 적용이 유일한 개방 기회라는 점, 판별 시그니처(Defer mic applied 뒤 Agent listening enabled 부재)를 정리. fallback 축은 stopSession이 리셋하지 않고 shouldActivate에 configured가 없어 무사했고, 그래서 부재 시나리오 검증만으로는 안 잡혔다. 수정은 상위가 예약 시점 configured를 configuredIntent 인자로 하향 전달. 재랜딩 중 Hermes 리뷰로 presence race 2건 추가 수정 — snapshot이 단조 비교를 건너뛰어 최신 push를 되돌리는 문제, 그리고 snapshot revision을 presence 계산 후에 읽어 낡은 상태에 최신 revision이 붙는 문제(redis-adapter로 cross-instance push가 로컬 ack보다 늦게 도착 가능해 성립). 재연결 시 revision baseline 리셋 방어 포함. 🟡 듣기 복원은 실기기 검증 완료, presence race 2건은 QA 대기
PPI-1167 — 사라진 '듣기 ON' 사건 [파일럿: 수사반장 + 웹툰]
PPI-1167 분석을 결론 선공개 → 비개발자 비유 요약(접이식) → 웹툰 6컷(순수 HTML/CSS 말풍선 패널) → 수사 일지 서사 → 증거 블록 구조로 재구성한 문서 포맷 파일럿. 내용은 원본 분석과 동일하며, 스토리텔링·웹툰 포맷의 가독성 효과를 원본과 비교 검증하기 위한 문서. 채택 시 bugs 신규 문서 표준 템플릿으로 승격 예정
두 번 잡은 범인 — TTS 녹음 합본 겹침 사건 [파일럿: 수사반장]
TTS 녹음 합본 겹침(6/30)과 최종 해결(PPI-1098, 7/3)을 하나의 수사 시간순 서사로 재구성한 포맷 파일럿 — 웹툰·비유 요약 없이 서사 재구성만 적용한 최저 비용 기본형이며, 이 모드가 bugs 문서 표준으로 채택돼 2026-07-29 전체 반영됐다. 원본은 '최종 해결이 위, 폐기된 1차 수정이 아래' 역순 구성인데 이 파일럿은 시간순으로 재배열해 1차 수정이 왜 나왔고 왜 부족했는지가 서사로 이어지는지를 검증한다. 진범은 ffmpeg 옵션이 아니라 구조 — 갭 있는(턴제 무음) 두 라이브 RTP 입력을 한 ffmpeg가 동시에 읽으며 정렬하려는 것 자체. -use_wallclock_as_timestamps를 켜면 무음 갭은 보존되지만 read-starvation으로 아동 턴이 밀리고, 끄면 밀림은 없지만 무음 갭이 붕괴해 핑퐁이 연속 발화가 병합되는 시소 구조라 옵션으로는 어느 쪽도 내려놓을 수 없었고, 최종 해결은 입력별 독립 ffmpeg 기록 + 후처리 정렬 믹스. TTS 모드가 새 버그를 만든 게 아니라 긴 연속 발화 패턴이 서버 녹음 합본의 구조적 결함을 가장 크게 드러냈을 뿐이라는 점이 핵심.
소리는 어디로 갔을까 — 3아동 수업 시작 소리 안 들림 [파일럿: 웹툰]
3아동 소리 안 들림 원인 분석을 웹툰 스트립 중심으로 재구성한 포맷 파일럿(채택 완료) — 타팀 공유·비개발자 독자를 겨냥해 웹툰을 본문 설명의 주 수단으로 승격했고, 전체 일괄이 아니라 타팀 공유·교훈이 큰 사건 문서에 선별 적용한다. 세 사례가 서로 다른 원인이라는 것이 핵심 교훈이라 사례당 2컷씩 3에피소드 6컷으로 나눴다. 김태희(iPhone)는 iOS 출력 sink/WebRTC loopback 라우팅 실패, 윤단우(Android)는 탭 백그라운드 전환에 따른 미디어 정지, 이진서(Windows)는 기본 출력의 Bluetooth 전환. '수도관' 비유 3줄 요약과 함께, 판별 근거 타임라인·운영 판별 체크리스트는 원본 그대로 하단에 보존한다. 같은 신고 문구 뒤에 세 가지 다른 범인이 있으므로 공통 장애를 가정하지 말라는 교훈 문서.
영상 loopback 오디오 먹먹함 — 전화 대역 원인 분석 (PPI-1170)
iPad(CriOS 150 / iOS 26.5.2) 아동 세션에서 영상 오디오가 먹먹하게 들리는 원인을 실기기 스펙트럼·송신 stats 계측으로 좁힌 분석. 인코더는 요청한 256kbps를 끝까지 쓰는데 remote 대역 상한만 1.9~4.0kHz(전화 대역)로 좁고, source는 최대 9.1kHz까지 살아 있다. 비트가 남는데 대역만 좁다는 것이 대역 축소 주체가 코덱 비트 배분이 아니라는 결정적 근거. 코덱 협상(fmtp 전량 반영·channels 2·clockRate 48000), 소스/ctx 대역(12-20k 비중 0 아님 → ctx 48kHz), BWE 비트레이트 하락(transport-cc·goog-remb 제거 후에도 유지), 적응성 열화(첫 샘플 정상은 remoteRms 0.002 램프업 노이즈), Bluetooth HFP(BT 미연결·내장 스피커 재현, 직결은 정상)를 모두 기각하고 브라우저 WebRTC 오디오 처리가 음성 경로로 동작하는 층으로 확정. 이 층은 웹 API로 접근 불가하므로 덕킹 면제(loopback)와 풀밴드는 웹에서 동시 성립 불가 — JUCE의 AVAudioSession 한 줄 해결책에 해당하는 레버가 웹에는 없다. 즉시 대응은 입장 화면 Loopback OFF(영상만 직결, TTS는 loopback 유지로 음량 균형 보존)이고 대가는 덕킹 약 12dB 감쇠. 게인 보상 실용 상한 +3~6dB(클리핑), EQ presence 2500Hz 완화책, jitterBufferApplied null(CriOS 수신 버퍼 고정 무효) 부수 발견 포함. 🟡 원인 층 확정·처방 선택 대기
텍스팅 수업 진행자 부재 시 AI 듣기 OFF — 원인 및 monitor presence fallback 설계
텍스팅 수업은 AI 입력을 듣기 OFF로 두고 진행자가 아동 음성을 듣고 텍스트로 대신 주입한다. 진행자가 없거나 이탈하면 음성·텍스트 입력이 모두 사라지는 원인을 정리하고, 미디어 서버의 유효한 monitoring-claim을 presence SSOT로 삼아 모든 반복 AI 세션 start 직전 snapshot 조회, 수업 중 실시간 변경 이벤트, absent/unknown fail-open, configured listening과 임시 fallback 분리로 해결하는 미구현 설계.
3아동 수업 시작 소리 안 들림 — iPhone·Android·Windows 원인 분석
김태희(iPhone Chrome), 윤단우(Android Chrome), 이진서(Windows Chrome)의 수업 시작 단계 영상·핑퐁이 무음 신고를 클라이언트 로그로 비교 분석. 세 사례 모두 음원 생성·디코딩 실패가 아니라 실제 출력 단계의 서로 다른 문제로 판정했다. 김태희는 iOS setSinkId 사용자 제스처 오류와 WebRTC loopback source RMS 존재·remote RMS 0 및 receiver 연결 실패가 겹친 출력 라우팅 장애, 윤단우는 시작 약 8초 뒤 탭이 hidden으로 전환되며 영상이 7.999초에 고정된 백그라운드 재생 정지, 이진서는 Windows 기본 출력이 Realtek에서 Bluetooth SRS-X11로 전환된 장치 라우팅 문제. 공통 배제 근거, 아동별 시간축, 확신도와 운영 판별 체크리스트 포함.
저사양 Android — 아바타 pause() 후에도 DECODE_OVERLOAD 지속, video 언마운트로 디코더 해제 (PPI-1162)
4GB Android(Chrome 150, isLowPerformanceDevice:true 로그 확증) 게스트가 두부핑퐁 2회기(하랑대화)에서 자료 영상 재생 중 DECODE_OVERLOAD WARN 67건, 전 영상 재생 내내 프레임 ~50~65% 드롭(실질 ~15fps). "자료 영상 중 아바타 pause()" 최적화(PPI-1143)가 이미 적용됐는데도 재발. 근본 원인(유력): 안드로이드 일부 기기에서 HTMLVideoElement.pause()는 재생 클럭만 멈추고 MediaCodec 인스턴스·HW 디코더 슬롯을 해제하지 않아, 마운트 유지된 idle/talking video가 src를 문 채 슬롯을 점유 → 자료 영상 디코더가 SW 폴백/스래싱. 판정 근거: 전 이벤트 readyState 2·networkState idle·bufferedAhead 39~51s라 네트워크 아닌 디코드 병목 확정(rtt 흔들려도 패턴 균일). 수정(PPI-1162, PR #923): isLowPerformanceDevice일 때 아바타 숨김 시 idle/talking video 언마운트(src 해제=디코더 teardown), remount 시 autoPlay로 talking이 발화 아닌데 재생되는 사이드 이펙트를 use-avatar-rms 루프에서 pause 보완. 콘텐츠 실제 해상도(480p/720p)는 이 export로 미확정(스냅샷에 videoWidth 없음, 캐시 키가 base 파일명)→720p면 480p 배선이 지배 레버, PPI-1162는 보완. 🟡 구현 완료·실기기 검증 대기
iPad 백그라운드 복귀 시 영상 loopback 오디오 무음 — 원인 분석 및 수정 (PPI-1158)
iPad(CriOS) 게스트가 인트로 영상(오프닝송)을 재생하는데 화면·재생시간은 흐르고 muted:false인데도 소리만 안 난 사례(아동 "소리가 안 들려요" 신고). 원인: iPad에서 영상 오디오는 덕킹 회피(PPI-1101)를 위해 createMediaElementSource로 WebRTC loopback 그래프에 우회되며, 이 그래프는 AI세션과 공유하는 AudioContext가 running일 때만 소리를 낸다. 백그라운드 진입 시 iOS가 ctx를 suspend하지만 포그라운드 복귀(visibilitychange)에는 resume 트리거가 전무해(기존엔 명시적 호출·pointerdown/touchend 제스처뿐), 복귀 후 탭이 없으면 ctx가 suspended로 남아 무음. 직결 폴백(hub→ctx.destination)도 같은 suspended ctx를 타고 createMediaElementSource는 되돌릴 수 없어 무력 → 유일한 복구는 ctx를 running으로 되돌리는 것. 무음 워치독은 "source 신호 있음+remote 무음"만 잡아 ctx suspended(source RMS도 0)는 사각지대라 경고 0건. AUDIO_PROBE suspectedSilent:false는 매번 throwaway ctx라 판정 무의미. 수정(PR #921): VideoAudioLoopback.resume() 추가(suspended ctx resume+정지 audio 재생 복구), meet-video·use-ai-session에서 visibilitychange→visible 시 resume(TTS 무음까지 커버), 유닛 테스트 3건. 시간순 인과 다이어그램·실기기 재현/검증 시나리오·후속(탭 유도 안전망·diag ctx state 기록) 포함. 🟡 코드 반영·실기기 검증 대기
MeetVideo loopback 오디오 출력 — sink 라우팅 원인 분석 및 수정
iOS 게스트에서 영상은 재생되지만 소리가 출력되지 않은 문제. loopback 전환 뒤 실제 출력자가 내부 Audio 엘리먼트인데도 sink 설정을 원본 video에만 적용한 것이 원인이며, 사용자 제스처 안에서 실제 출력 엘리먼트에 setSinkId를 적용하도록 수정한 내용과 회귀 방지 검증을 정리.
TTS Blob src 교체 AbortError — AI 발화 말끊김 세션 로그 원인 분석
7/20 prod 게스트 세션에서 TTS local audio element가 2회 AbortError(“play() request was interrupted by a new load request”)를 남긴 사례. 이전 청크 재생 완료 전 새 Blob URL을 동일 audio.src에 즉시 설정해 기존 play 요청이 취소되는 직접 원인을 로그와 tts-player 코드로 대조. 청크 도착 간격은 촉발 조건일 뿐 근본 원인은 단일 엘리먼트 src 덮어쓰기 경합임을 구분하고, FIFO 재생 큐·명시적 취소·AbortError 분류를 권고안으로 정리. ai-audio SFU 송신 stall·AudioContext 출력 정지·입력 noise reduction 불일치는 별도 신호로 분리.
저사양 Android 720p 컨텐츠 DECODE_OVERLOAD — lowPerf 플래그 미배선 두 실패 모드 (정이안 3회기)
수업 중 반복 멈춤·끊김(위치 옮겨도 재발) VOC. 근본 원인: isLowPerformanceDevice가 MeetVideo/MeetImage에 배선되지 않아(guest-layout-content.tsx:203에서 아바타에만·split 모드만) 컨텐츠 영상이 2GB Android에서도 480p가 아닌 720p 원본으로 재생됨 — RESOURCE_CACHE 요청 파일명에 _480p 없음으로 확증. 720p@30=H.264 Lv3.1 한계라 프레임 31% 드롭. 두 실패 모드: ①아바타 없는 긴 영상=진행성 과부하(31% 드롭·완주), ②아바타 있는 영상=4~5초 하드 프리즈(숨겨진 아바타 동시 디코딩 가중). 진행자 녹화 멈춤은 별개 원인(버퍼 starvation, bufferedAhead 0·dropped 0, 8GB 정상 기기, kick 전 발생). 서버 녹화는 오디오 전용 mp3. 해결: MeetVideo/MeetImage에 lowPerf 플래그 배선(핵심)+아바타 full모드 480p·hidden pause. 🟡 CAUSE
영상 currentTime 앞으로 점프 (SEEK_REBUFFER) → near-end 조기 종료
52.97초 영상(video_newpingpong04_newstep_02)이 재생 시작 24.7초 만에 종료 처리된 사례(김서연 4회기, Android 10/Chrome/RAM 4GB 저사양). 재생 중 currentTime이 약 1.8s→33.8s로 순간 점프(약 32초 건너뜀)하면서 위치 시계가 끝(52.97s)에 벽시계보다 훨씬 일찍 도달, near-end fallback(currentTime≥duration−0.1)이 onEnd을 조기 호출해 다음 스텝으로 자동 전환됨. 핵심 증거: SEEK_REBUFFER 시점 totalVideoFrames=149(=30fps로 5초치 정상 디코딩)인데 currentTime=33.826 → 디코더가 아니라 위치 시계만 약 29초 앞섬 = 불연속 seek(native seeking 이벤트가 SEEK_REBUFFER로 분류된 게 그 결과). 앱/소켓/호스트 seek(guest는 monitor 전용 seek뿐, 로그 0건)·영상 파일(ffprobe 정상)·네트워크(blob 전체 버퍼링)·탭 백그라운드(visible)·메인스레드 프리즈(점프 구간에 다른 로그 정상 발생) 전부 배제. 원인은 저사양 기기 미디어 파이프라인의 clock/seek 이상이며 스텝 전환 직후 AI teardown+H264 디코드 시작 경합 정황, 세션 내 Video waiting 9건 전부 SEEK_REBUFFER. VideoEndWatchdog은 near-end에 의해 정상 disarm(오발화 아님). Watchdog DECODE_OVERLOAD 미발화 건과 정반대 실패 모드(끝에 너무 일찍 도달 vs 도달 못 함).
조기 입장(대기실) 경로에서 카메라 deviceId 유실 → 가상 카메라(Mirametrix) 캡쳐
[PPI-1138 원인 ① 수정 완료 · commit 3222e299] 조기 도착으로 미니게임 대기실을 거쳐 자동 입장한 아동(LG gram/Windows/Edge)에게서 수업 페이지가 실물 LG 카메라 대신 Mirametrix Virtual Camera(LG gram 번들 Glance 시선추적 SW의 가상 카메라)를 잡아 아동 영상이 캡쳐되지 않은 사례. 근본 원인 두 겹 — ① 대기실 자동입장 콜백 handleWaitingRoomComplete의 의존성 배열이 [roomId]뿐이라 발생하는 stale closure가, 마운트 시점(selectedVideo=null)의 tryV2Flow를 고정해 8분 뒤 카운트다운 종료 시 client-guest-device-ids.videoDeviceId를 null로 저장(그 사이 auto-select로 LG 카메라가 선택됐어도 콜백 미갱신), ② 수업 페이지는 PROBLEMATIC_CAMERAS 필터를 재적용하지 않고 deviceId가 없으면 video:true로 폴백해 OS 기본 카메라(Mirametrix)를 그대로 획득. Edge 브라우저 문제 아님, 제시간 직접 입장 경로에서는 미재현. 수정: deps에 selectedVideo/Audio/AudioOutput 추가로 원인 ① 해소(WaitingRoom effect가 onCountdownComplete를 deps에 포함해 부모·자식 조합이 올바르게 맞물림), 원인 ②(수업 페이지 label 방어막)와 극단 레이스는 이 커밋 범위 밖으로 잔존. 시간순 인과 다이어그램·코드 경로·재현 조건·수정 검증 포함.
iOS WebKit clone track 무음 — 현재 상황 · 원인 · 근본 해결방안 (PPI-1121)
iOS WebKit에서 MediaStreamTrack.clone()으로 만든 mic 트랙이 확률적으로 live인데 무음(zero-audio)이 되는 문제의 종합 문서. 현재 상황(관측 사례 전부 iPad, 새로고침 무효·kick만 회복), 원인(박서준 7/9 로그 대조 확진 — AI 전사로 발화 증명된 창에서 송신 outbound audioEnergyDelta=0, SFU 트랙은 WebAudio destination의 clone의 clone), WebKit 공개 이슈 대조(Bug 228255 audio clone flaky는 인접 사례이나 1:1 아님 — 우리 건은 live clone의 zero-audio 송신으로 mediacapture-main 표준 위반 동작, 단일 known bug 단정은 불가), 해결방안 3단계(① 자가회복 가드 적용 완료 60a8195c: VAD 발화 창 energy 0 → producer 재발행, micGuard 튜너블 ② producer clone 제거 실험: stopTracks:false로 clone 단계 축소 ③ 소비자별 MediaStreamDestination 분기로 clone 완전 제거).
iOS 덕킹 — TTS 모드 감쇠 원인 분석 및 WebRTC loopback 우회 (PPI-1101)
같은 iPad 내장 스피커+마이크 조건인데 Realtime 모드는 감쇠가 작고 TTS 모드는 큰 이유 분석. 마이크 EC로 voice-processing(통화 세션)이 활성일 때, Realtime의 WebRTC 원격 트랙(srcObject)은 far-end 통화 음성으로 렌더되어 덕킹 면제 + AEC 레퍼런스인 반면, 기존 TTS의 Blob URL audio.src 재생은 통화 밖 "기타 미디어"로 분류되어 시스템 덕킹을 맞는다. EC OFF/마이크 OFF는 iPad EC-AGC 번들 문제와 아동 음성 전달 위험 때문에 폐기. 최신 적용안은 TTS 로컬 출력만 MediaStreamAudioDestination → local RTCPeerConnection sender/receiver → receiver ontrack remote MediaStream → audio.srcObject 경로로 바꾸는 WebRTC loopback 우회이며, 실패 시 Blob URL로 폴백한다.
아동측 미디어·세션 이상 전반 — LogRocket 원인 판별용 로깅 추가 범위 (무음 · 자동전환 멈춤 · 재접속 미디어 연결)
7/7 원준호 40회기 "접속 직후 소리 안 들림"(인트로 무음, 신고 3회·리로드 3회 끝 회복) 조사에서 드러난 관측 공백을 계기로, 아동측 반복 신고 3계열(무음·영상 종료 후 자동전환 멈춤·재접속 시 카메라/마이크 연결 문제)의 진단 로깅 범위를 정의. A~H 8개 영역: AudioContext 프로브(resume 후 currentTime 전진 = 하드웨어 클럭 신호), 신고 시점 스냅샷(GUEST_ISSUE_REPORT payload 동봉 → Loki 조회), MEET_VIDEO/AI 오디오 헬스, SFU 구간 절단용 게스트 outbound-rtp·모니터 inbound-rtp/audioEnergy(relay clone 무음 확정 판별), 자동전환 체인 보완(watchdog reset 사유·emit ack·전환적용 확인·timeupdate 스톨), 재접속 미디어 재획득(getUserMedia error.name 분류·track mute/unmute/ended 수명 추적·리로드 루프 감지·devicechange). 증상별 로그 서명 매트릭스 3종과 우선순위, createMediaElementSource 재라우팅 금지 등 제약 포함. 코드 미반영 범위 제안 문서.
PPI-1215 영상 재생 지연 및 자동전환 보강
video_action_balancegame_02 모니터·게스트 병합 로그 분석. 게스트 DECODE_OVERLOAD waiting 반복으로 모니터 종료 시 약 29초 뒤처져 native ended와 onEnd 체인이 도달하지 못한 원인을 확인하고, 선행 videoSrc 변경·listener 해제·네트워크 starvation 가설을 배제. guest-only wall-clock catch-up seek와 누적 지연 watchdog fallback의 로컬 구현 및 검증 한계를 정리.
녹음 post-mix 손상 stem — 14GB 거대 MP3 무한 인코딩·디스크 100% 폭주 (PPI-1106)
ppi-socket-prod 루트 디스크 20GB가 단일 녹음 MP3(14GB, 계속 증가)로 100% 참. 세션 중 stem ffmpeg 재시작 SIGKILL로 finalize 안 된 2.8KB 손상 OGG가 ffprobe에서 start_time=0·duration≈17억초로 "성공적으로" 읽혀 기존 검증(size>0·duration>0·null 체크)을 전부 통과, base=min(start_time)이 0(=1970년)으로 무너지며 정상 stem들의 adelay가 epoch값 그대로(≈56년)가 됨 → amix duration=longest가 56년치 무음을 128kbps CBR(초당 16KB)로 무한 인코딩. 거대 MP3의 실체는 손상 stem 반복 병합이 아니라 정상 stem 앞에 붙은 무음 패딩. "타임스탬프 없음"과 "1970년"이 구분되지 않는 wallclock 설계의 함정 + PPI-1098 정렬 도입 이후 필연이던 스위스 치즈 사고. hotfix 4중 방어: duration 12h 상한(isPlausibleStemDurationMs), start_time 녹음시작 wallclock ±12h 범위 검사(planPostMix 순수 함수 추출), ffmpeg -t 6h 출력 상한(buildPostMixArgs), 실행 15분 타임아웃+SIGKILL. TDD 12케이스(prod 시나리오 재현), 캡션 좌표는 mp3T0Ms 경유로 함께 치유. PR #837.
TTS 재생 스톨 — onended 미발생으로 인한 재생 큐 영구 블로킹 원인 분석
7/5 prod iPad 게스트 2세션(4회기·16회기)에서 핑퐁이 무반응 27~41초 → 강제 재접속의 원인 분석. TTS 요청·응답(Typecast fetch)과 OpenAI 응답 생성은 전 구간 정상이었고, 재생 중 청크의 WebAudio onended가 미발생하며 playing=true 고착 → 이후 모든 발화가 재생 큐에 블로킹. 전조로 2~3초 청크가 10초 재생(메인스레드 정지 서명: 전 로거 10초 공백 + 콜백 동시 해빙). tts-player 취약점 4종(onended 단일 의존, watchdog 부재, resume 도달 불가, iOS interrupted 미처리)과 suspended-start 변종(1.92/1.93) 관계, 로그 판별법, watchdog·statechange·start 전 상태 보장 수정 방향 정리.
영상 종료 후 자동전환 미발생 세션로그 원인 분류 및 Watchdog 분석
7개 세션 로그를 기준으로 guest-video-end emit 누락 지점을 재분류. DECODE_OVERLOAD로 media currentTime이 지연된 케이스, host step change로 watchdog이 reset된 케이스, complete force kick 선행, 탭 hidden/transport close, 외부 kick을 시각화하고 VideoEndWatchdog이 왜 발화하지 않았는지 코드 조건과 타임라인으로 정리.
iPad Pro 내장 스피커/마이크 영상 오디오 감쇠 원인 분석
iPad Pro 내장 스피커+내장 마이크에서 MeetVideo Blob URL 영상 재생 중 대화인식 이벤트 발생 시 오디오가 작아지는 현상을 WebKit 캡처 세션, echo cancellation, External STT, Realtime VAD 관점에서 분석.
TTS/Realtime 녹음 합본 겹침·밀림 — 원인 분석 및 최종 해결(입력별 독립 ffmpeg + 후처리 믹스)
녹음 합본에서 아동 턴이 뒤로 밀려 무음/겹침이 되거나 핑퐁이 연속 발화가 병합되던 문제. 근본 원인은 2겹 — 단일 ffmpeg가 두 RTP(child mic + ai-audio)를 함께 읽을 때 wallclock을 쓰면 조용한 입력의 read-starvation으로 아동 턴이 밀리고, 안 쓰면 무음 갭 붕괴로 AI 발화가 병합됨(한 ffmpeg론 트레이드오프 불가피). 최종 해결(PPI-1098): 입력별 독립 ffmpeg가 각 stem을 -use_wallclock_as_timestamps -copyts로 절대 start_time과 함께 기록하고, 정지 시 각 stem을 절대시각 기준 adelay로 정렬해 amix(후처리 믹스). 캡션 wallclock 전환, 종료 flush 긴 타임아웃, stem 무결성 필터(손상 stem 드롭), 교체 경계 old consumer pause(중복 기록 방지) 포함. TTS·Realtime 실측 검증 완료. 클라이언트 로컬 재생은 Blob URL 방식 유지. PR #822.
자동전환 OFF 대기 중 설정 후 영상이 자동전환되는 이슈
게스트가 먼저 수업 대기(고양이 화면)에 있고 호스트가 이후 수업 화면에서 자동전환 OFF로 변경한 뒤 시작하면 영상 종료 시 다음 스텝으로 넘어가는 이슈. 원인은 호스트 설정 모달의 로컬 settings.autoTransitionEnabled와 SFU room autoTransitionEnabled가 분리되어, 수업 시작 전 OFF가 room 상태로 전파되지 않고 서버 기본값 true가 guest-approved2 payload로 전달되는 경로.
오디오 분석 nTurns 재생 — 턴 끝부분 잘림 원인 분석 + 꼬리 마진 수정
핑퐁이 세션 로그 오디오 분석 컬럼의 nTurns 합본 음성 재생 시 각 턴 끝부분이 잘리는 VOC. 합본/인코딩 경로(combineAudioBlobsToWav·encodeWavMono)는 decodeAudioData가 컨텍스트 길이와 무관하게 전체 PCM을 반환하고 mono 전체를 복사하므로 샘플 손실 없음을 코드로 검증해 범인에서 배제. 진짜 원인은 캡처 종료 타이밍 — 발화-종료 트리거(소켓 컨트롤 채널 guest-speaking-stop/ai-speaking-stop)가 녹음 대상인 원격 WebRTC 스트림(jitter buffer 지연)보다 먼저 도착해, 꼬리 오디오가 트랙에 렌더되기 전 recorder.stop()이 동기 호출되어 매 턴 일관되게 끝부분 절단. 분석 전송용 audioBlobToWav도 같은 입력이라 LLM도 뒷부분을 못 들음. recorder.start(500) 타임슬라이스는 stop()이 flush하므로 원인 아님. 수정: use-guest-audio-capture.ts에 STOP_TAIL_MARGIN_MS=500 지연 stop 도입(flushStop/scheduleStop 분리), 마진 중 발화 재개 시 cancelScheduledStop으로 예약 취소해 한 턴 유지, 언마운트 타이머 정리. PPI-1082 로컬 적용.
아동247 24회기 — 영상 완료 자동전환 실패 2건 재현 경로
2026-06-23 prod 아동247 24회기에서 영상 재생 완료 시 자동 전환이 실패한 2건을 LogRocket 아동·진행자 세션과 Grafana ppi-socket-prod 로그로 대조. 영상 시작 이벤트(Guest video play started)는 서버에 9건 도달했지만 guest-video-end/video-end 계열은 0건, 이후 진행자 엘리스가 Host changed step으로 수동 전환한 흐름을 확정. MeetVideo.onEnded → GuestLayoutContent.handleVideoEnd → notifyVideoEnd/goToNextStep 경로와 실패 지점 포함.
녹음 캡션 위치 어긋남 — TTS 모드 아동 캡션 누락 원인 분석
"듣기 OFF + 진행자 타이핑"(=TTS/External STT) 수업에서 녹음 캡션 위치가 어긋나는 VOC. 직접 측정 결과 고장 caption.json은 user 캡션 0개(정상은 49개) — External STT가 아동 전사를 type:"text"/"stt_verified"로 보내는데 녹음 캡션 수집기는 type==="audio"만 받아 아동 전사 전량 탈락. 듣기 토글은 무관(인과 반전). 중첩 녹음은 TTS 이중출력(릴레이+스피커) acoustic echo로 핑퐁이가 ai/child 트랙 양쪽 인입(세그먼트 중첩은 반증). 추가로 wall 19분 중 3.5분이 전환 시 드롭아웃... [2026-06-25 업데이트] 후속 케이스(아동333 24회기)로 Realtime 모드(user 캡션 정상)에서도 어긋나는 별개 2차 원인 확인 — 캡션 좌표(OGG granule, 195s)와 실제 mp3(무음 붕괴, 119.8s) 타임라인 불일치. 수정 적용(PR #786): transcode aresample=async로 갭 복원·캡션 필터 확장·마이크 AEC 명시. [echo 가설 정정] AEC 배포 후 테스트 녹음도 23초+ 양방향 섞임 지속 → 동시 이중피치 분석 결과 acoustic echo가 아니라 '동시 발화 겹침'(TTS 끼어들기 미차단)이 주원인. [최종 해결] barge-in은 끼어들기 OFF와 모순·불안정으로 폐기, stem 분리는 과함 → 녹음 amix에 child sidechaincompress ducking(약 1줄) 적용해 아동 발화 중 핑퐁이 자동 감쇠 = 아동 우선 무중첩 녹음(realtime/tts 확인). PR #786.
아동005 27회기 아바타 매칭 오류 — 태양 talking 전환 시 지우 렌더
지우·태양 2아바타 회기에서, 체크아웃(끝인사) 단계의 태양(taeyang_v2) talking 전환 시 지우(jiwoo_v2) talking 영상(avatar_jiwoo_v2_talking_0.mp4)이 잠깐 렌더되는 VOC. 게스트 측에서만 발생...
아동070 1회기 — 아동 소리 안 들림 원인 분석
텍스팅 수업에서 진행자에게 아동 목소리가 전달 안 된 VOC(영상·말풍선은 정상). 전달 경로는 전부 무결 — 서버 Loki 직접 조회로 producer/consumer 34/34 매칭·transport connected(RTT 15~25ms)·pause/error/fail 0 확인...
☠️ 아동004 29회기 수업 중단 — 아동 단말 인터넷 단절 원인 분석
해골 이슈(외부 요인·코드 결함 아님). 수업 시작 2분 51초 뒤 아동 접속 끊김(08:02:51 UTC). NETWORK_QUALITY가 user-network로 진단 — gstatic·1.1.1.1·/api/health 3-프로브가 3초 타임아웃으로 동시 실패(externalLatency...
갤럭시 탭 S10 FE simulcast 인코딩 부하 검증 — 레이어별 로깅 + 1레이어 토글
S10 FE가 카메라를 simulcast 3레이어(100k·300k·900k)로 동시 인코딩할 때 실제 단말 부하가 걸리는지, 그 부하가 DECODE_OVERLOAD(영상 끊김)...
카메라 인코딩 경량화 — simulcast 고화질 레이어 낭비 개선
게스트 동시부하(영상 끊김 DECODE_OVERLOAD·오디오 끊김)의 큰 덩어리인 카메라 인코딩 경량화 방안. 아동 기기는 카메라를 simulcast 3벌(저270p·중540p·고1080p) 동시 인코딩(use-mediasoup-producer.ts:415...
DECODE_OVERLOAD 발생 감소 — 인코딩 파라미터 및 lowPerf 기준 개선
full:video 게스트 실제 부하 구조 분석. 6코어/8GB 기기가 lowPerf 기준 미달로 720p(1800k)를 수신해 DECODE_OVERLOAD 발생. 개선: ①480p fps 30→24...
VideoEndWatchdog DECODE_OVERLOAD 반복 stall 미발화 원인 분석
arm() 첫 tick이 예상 종료 시각에 예약 → stall-resume 반복 시 누적 지연 감지 불가. wall clock drift 체크(방안 1) 권장. 아동303 25회기·아동049 24회기·아동074 2회기 3개 세션 7회 이상 확인. 🔵 OPEN
고사양 240Hz 노트북 DECODE_OVERLOAD — 디코드가 아니라 present 파이프라인 stall
VOC "영상 재생 끊김"을 MSI Vector GP66 12UEO(i7-12650H·RTX3060·QHD OLED 240Hz·8GB, 로그 hardwareConcurrency 16·deviceMemoryGB 8와 일치)에서 분석.
자동전환 미반영 — 게스트 socket.io 업링크 26초 stall 원인 분석 및 로깅 추가
핑퐁이가 자동전환 멘트("그러면 같이 한번 찾으러 가보자!")를 말해 조건 매칭됐는데 진행자 모니터가 안 넘어가고, 진행자 수동 점프·새로고침으로 개입하다 아동이 직전 활동(하랑대화1)으로 회귀한 VOC. LogRocket 아동/진행자...
AI 발화 "지지직" — STT 캡처 WAV 클리핑(과레벨) 분석
핑퐁이(AI) 발화의 지지직을 STT 검증용 캡처 WAV(ai_stt_req19, 16kHz 무손실)로 분석.
gpt-realtime WebRTC 정적/버징 노이즈 — 6/12 발생 원인 분석
핑퐁이(AI) 발화에 간헐적 큰 정적/크래클(라이브 수업 ~50%, 20분당 2회, gpt-realtime v1.0·WebRTC). 6/12 발생인데 RMS 립싱크(#709)는 6/14에야 운영 배포 → 배제...
아동측 영상·오디오 끊김 — 동시 인코딩 부하 분석 및 simulcast 360p 계획
활동 영상 재생 중 아동측 화면+오디오 동시 끊김(VOC). DECODE_OVERLOAD 로그는 meet-video.tsx:554 catch-all 오분류(드롭 1%·풀버퍼·고사양인데도 라벨링). 영상 파일은 무죄 — 용량(20MB/2분)·포맷·플레이어·MSE/HLS 모두 기각...
ffprobe duration WARN 대량 발생 — 빈 OGG 세그먼트 / 캡션 probe 비대칭 원인 분석
ppi-socket WARN의 약 절반(ffprobe duration failed ~1,200건/24h, 대부분 End of file). 손상도 사고도 아님 — Invalid data(진짜 손상) 0건, 50개 방에 분산, 포트충돌·크래시 기여 ~8%뿐...
경과시간 09:08 → 18:03 점프 — 누적시간 이중 가산 원인 분석
모니터(호스트) 소켓의 짧은 끊김→재접속 직후 useCumulativeElapsed가 세션을 재조회하며 count 0→1. 같은 ~9분 구간을 "DB 종료세션(duration) + 살아있는 lessonStartedAt 라이브 카운터"가 이중 가산...
경과시간 03:49 → 02:29 감소 — 게스트 재접속 후 누적시간 회귀 분석
포커스뷰 헤더 타이머(useCumulativeElapsed = Σ DB세션 duration + (now − lessonStartedAt anchor))가 게스트 재접속 직후 ~80초 줄어든 현상. ✅ 코드 검증·결정론 재현으로 02:29 확정.
누적시간 회귀 03:49→02:29 — 재현 경로 파악 과정 (repro-path)
위 회귀를 repro-path 스킬(인테이크→코드검증→반증루프→결정론 재현)로 재현한 전 과정 메타 문서. 원본 1차 가설("anchor 재설정이 원인")을 코드 실행으로 부분 정정: 회귀의 게이트는 anchor 재스탬프가 아니라 hasLiveCurrentRecord=false(라이브 세션 레코드 부재) 창이며,...
누적시간 — 이탈시간 30초 규칙 적용 + 재접속 경과시간 회귀 수정
✅ 적용 완료. 재접속 이탈 시간 누적 규칙(≤30초 포함 / >30초 제외)을 코드에 반영 + 긴 단절 재접속 시 경과시간 회귀(03:49→02:29) 차단. 변경①(서버...
아동 알림 프리셋 트리거 race · 이미지 동기화 수정
스냅샷이 트리거보다 늦게 도착하는 race로 첫 트리거 누락·표시 중 이미지 교체·host/guest 이미지 불일치 → 첫 트리거 버퍼 재평가 + 표시 중 이미지 고정(seq) + 표시 중 진행자 재트리거 차단 + 트리거에 이미지 동봉으로 항상 일치
녹음 후처리 CPU Spike 분석 및 큐 처리 개선
mediasoup 녹음 activity 전환 끊김 원인 분석
노트북/데스크탑 DECODE_OVERLOAD 해결
영상 종료 이벤트 미발생 & NAVER 인앱 브라우저 분석
OpenAI Realtime 세션 시작 실패 원인 분석
PLC 기계음 오탐지 원인 분석 및 해결
setSinkId 시스템볼륨 우회 분석
사운드 이슈 종합 분석
모니터 측 게스트 마이크/AI 오디오 무음 원인 분석
아동274 아동 3회 접속 실패 — 마이크 무음 입력 / 영상 다운로드 실패 원인 분석
LogRocket 3세션 분석: 패드 마이크 무음 캡처 + 노트북 영상 fetch 실패, AI 출력은 정상
httpbin.org 503 / CORS 콘솔 에러 — 네트워크 품질 체크 원인 분석
외부 무료 서비스 503 다운이 원인, try/catch로 흡수되는 콘솔 노이즈. 리소스/AI/미디어 영향 0, 죽은 데이터라 외부 체크 제거로 정리
검색 결과가 없습니다.
본문 색인 중...