공통 인시던트 컨텍스트
2026-09-11 20:10:29 KST, Slack 채널 C0AKXLE61J5(#ceo_lead_align_input)의 스레드 1789104278.881179 에서 사용자가 봇을 멘션했다. 봇은 2초 뒤 "경과 531초 / 8분째 실행 중" 진행 카드를 올렸고(=이미 돌던 다른 런의 진행 상태), 약 3분 뒤 전혀 무관한 ReelBrain 티켓(exp3-cardnews-reunion-conditions, qa-delivery BLOCKED)의 ## Terminal phase summary 본문을 그 스레드에 게시했다. 게시된 두 메시지는 이후 수동 삭제했다.
로그: ~/.cli-jaw/logs/serve.log 19555-19670, jaw-serve.err. DB: ~/.cli-jaw/jaw.db messages 1666/1676/1677.
라벨 제안: 본문 상단 표기 참조.
제안 라벨: bug priority:P1 area:slack
제목
Slack 스트림이 닫힌 뒤 append/stop이 실패하고, 진행 카드 대신 본문이 새 메시지로 떨어진다
요약
진행 카드는 chat.startStream → appendStream → stopStream만으로 갱신돼야 한다. 스트림이 닫히면(message_not_in_streaming_state) 같은 ts를 chat.update로 이어가려 하지만 ts가 이미 없어 message_not_found가 나고 idle이 재시도만 반복한다. 카드는 멈추고 최종 본문은 sendSlackText가 항상 새 chat.postMessage로 올린다. 2026-09-11 20:10 KST CEO 스레드 오배송에서 무관한 ReelBrain 요약이 별도 메시지로 증폭됐다.
재현·증거
상태 머신: src/slack/progress.ts begin() L256–289 (startStream, unsupported만 postMessage), flush() L217–245 (native=appendStream, fallback=update), finish() L312–336 (stopStream→update), useMessageUpdates() L131–138. 요청 소유: progress-lifecycle.ts L205–222. 본문: send-only-client.ts L162 항상 postMessage. draft-stream.ts는 Slack native 경로가 아니고 chunk.ts는 본문 분할만 한다.
~/.cli-jaw/logs/serve.log(= jaw-serve.err 중복):
message_not_in_streaming_state 32회 (append 12, stop 20). stop 10회는 [slack:progress] 짝, 10회는 notice-transport.
- startStream
user_not_found 3회. update message_not_found 48회, 그중 47회가 19570–19662 한 줄 폭풍.
- 인시던트: 19555
[slack:in] C0AKXLE61J5: 너도 한번 해봐! → 19565 exited … 19613 chars → 19566 slack.post of:1 → 19567 appendStream streaming_state → 19570–19662 update not_found → 19669–19670 slack.post of:2.
단위 테스트(tests/unit/slack-progress-stream.test.ts L275–287)는 5분 후 스트림 종료 뒤 같은 ts update가 성공한다고 본다. 실서비스는 update도 not_found.
원인 분석
- Slack 스트림 TTL(~5분) 또는 이미 stop된 ts에 append/stop → streaming_state 오류.
- fallback은 교체 게시를 하지 않는다(L133–134 주석). 의도는 맞다.
flush()는 stopped_by_user만 remoteEnded. message_not_found는 멈추지 않고 startIdle() L246–254가 3.2초마다 dirty=true → 47회 재시도.
- 새 메시지의 실체는 진행 폴백이 아니라 본문 경로다. 카드 ts가 죽은 뒤 사용자에게 보이는 것은 postMessage뿐이고, 19613자는 chunk로 2통이 된다.
- 가설: “경과 531초 / 8분째”(
progress-activity.ts L33, L222–223)는 새 handle이 아니라 이미 ~531초 산 카드다. 그 스트림이 닫힌 뒤 오배송 본문이 같은 스레드에 추가로 떨어졌다.
수정 제안
flush()/finish()에서 message_not_found·cant_update_message면 remoteEnded=true, 타이머 중지, 후속 startStream/postMessage 금지. 카드는 조용히 포기.
- streaming_state 이후 append/stop 재호출 금지, fallback update 1회.
- 죽은 카드 빈자리를 새 진행 메시지로 메우지 말 것. 본문 post 여부는 타깃 가드(축 A/B)와 맞출 것.
- 대안: ~4분에 선제 stop 후 update 전환. 만료 후 카드 재생성 금지.
metrics.ts에 stream_state_lost 카운터. 테스트에 “expiry 후 not_found → 재시도 0, postMessage 0”.
영향 범위
Slack 진행 카드·복원(notice-transport.ts). Discord draft-stream은 비대상. 오배송 타깃 바인딩은 별 이슈.
검증 방법
기존 stream 테스트 + not_found 시 재시도 0. 5분+ 잡에서 새 진행 메시지가 없고 idle이 멈출 것. 이 이슈만으로 라이브 Slack에 쓰지 말 것.
관련 이슈 후보
- 멘션이 다른 런 본문을 C0AKXLE61J5로 보낸 타깃/세션 바인딩
- 진행 카드와 본문 postMessage의 이중 소유
- notice-transport 중복 stopStream
같은 인시던트에서 갈라진 이슈: #742 · #743 · #745
제안 라벨:
bugpriority:P1area:slack제목
Slack 스트림이 닫힌 뒤 append/stop이 실패하고, 진행 카드 대신 본문이 새 메시지로 떨어진다
요약
진행 카드는
chat.startStream → appendStream → stopStream만으로 갱신돼야 한다. 스트림이 닫히면(message_not_in_streaming_state) 같은 ts를chat.update로 이어가려 하지만 ts가 이미 없어message_not_found가 나고 idle이 재시도만 반복한다. 카드는 멈추고 최종 본문은sendSlackText가 항상 새chat.postMessage로 올린다. 2026-09-11 20:10 KST CEO 스레드 오배송에서 무관한 ReelBrain 요약이 별도 메시지로 증폭됐다.재현·증거
상태 머신:
src/slack/progress.tsbegin()L256–289 (startStream, unsupported만 postMessage),flush()L217–245 (native=appendStream, fallback=update),finish()L312–336 (stopStream→update),useMessageUpdates()L131–138. 요청 소유:progress-lifecycle.tsL205–222. 본문:send-only-client.tsL162 항상 postMessage.draft-stream.ts는 Slack native 경로가 아니고chunk.ts는 본문 분할만 한다.~/.cli-jaw/logs/serve.log(=jaw-serve.err중복):message_not_in_streaming_state32회 (append 12, stop 20). stop 10회는[slack:progress]짝, 10회는 notice-transport.user_not_found3회. updatemessage_not_found48회, 그중 47회가 19570–19662 한 줄 폭풍.[slack:in] C0AKXLE61J5: 너도 한번 해봐!→ 19565exited … 19613 chars→ 19566slack.postof:1 → 19567 appendStream streaming_state → 19570–19662 update not_found → 19669–19670slack.postof:2.단위 테스트(
tests/unit/slack-progress-stream.test.tsL275–287)는 5분 후 스트림 종료 뒤 같은 ts update가 성공한다고 본다. 실서비스는 update도 not_found.원인 분석
flush()는stopped_by_user만remoteEnded.message_not_found는 멈추지 않고startIdle()L246–254가 3.2초마다 dirty=true → 47회 재시도.progress-activity.tsL33, L222–223)는 새 handle이 아니라 이미 ~531초 산 카드다. 그 스트림이 닫힌 뒤 오배송 본문이 같은 스레드에 추가로 떨어졌다.수정 제안
flush()/finish()에서message_not_found·cant_update_message면remoteEnded=true, 타이머 중지, 후속 startStream/postMessage 금지. 카드는 조용히 포기.metrics.ts에 stream_state_lost 카운터. 테스트에 “expiry 후 not_found → 재시도 0, postMessage 0”.영향 범위
Slack 진행 카드·복원(
notice-transport.ts). Discord draft-stream은 비대상. 오배송 타깃 바인딩은 별 이슈.검증 방법
기존 stream 테스트 + not_found 시 재시도 0. 5분+ 잡에서 새 진행 메시지가 없고 idle이 멈출 것. 이 이슈만으로 라이브 Slack에 쓰지 말 것.
관련 이슈 후보
같은 인시던트에서 갈라진 이슈: #742 · #743 · #745