Skip to content

[slack] 실행 중 런의 최종 응답이 요청 스레드가 아니라 전역 lastActive 슬롯으로 나간다 #742

Description

@cosmosjeon

공통 인시던트 컨텍스트
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:P0 area:slack area:messaging

최종 응답 목적지가 요청 envelope가 아니라 lastActive 전역 슬롯에서 결정된다

요약

실행 중 런의 최종 텍스트가 그 런을 시작한 대화가 아니라, 가장 최근에 봇에게 말을 건 Slack 스레드로 나간다. 2026-09-11 20:10 KST 오배송은 웹 origin ReelBrain 런(b25ca0b8)의 종료 텍스트가, 방금 멘션이 덮어쓴 lastActive(C0AKXLE61J5 / ts=1789104278.881179)로 포워딩된 사례다.

재현·증거

  1. 인바운드가 채널당 슬롯 1개를 즉시 덮는다. src/slack/bot.ts:1508-1509
setLastActiveTarget('slack', target);
setLatestSeenTarget('slack', target);
  1. 웹 런 종료 agent_done에는 origin/text만 있고 target이 없다. src/agent/lifecycle-handler.ts:821 (print), :709-716 (native)

  2. Slack agent_done 포워더는 페이로드가 아니라 지금 시점의 lastActive를 읽는다. src/slack/bot.ts:1822-1825, src/slack/forwarder.ts:70-76, src/messaging/forwarder-origin.ts:32-38

  • origin === 'slack'만 skip. 웹/CLI는 통과.
  • const target = opts.getLastTarget()getLastActiveTarget('slack').
  1. 이번 사건 원문
  • DB messages 1666: role=user, cli=web, session=b25ca0b8 (exp3 qa-delivery)
  • 1676: [👤 jun] 너도 한번 해봐! session=6c6e4301 created_at=2026-09-11 11:10:31 UTC
  • 1677: ## Terminal phase summary … exp3-cardnews-reunion-conditions session=b25ca0b8 created_at=11:13:24
  • serve.log:19555 [slack:in] C0AKXLE61J5: 너도 한번 해봐!
  • :19565 [jaw:main] exited code=0, text=19613 chars
  • :19566 {"event":"slack.post","target":"C0AKXLE61J5","threaded":true,"index":0,"of":1} — 같은 줄에 outbound.send / via:agent 없음 → dispatch/forwarder 직접 sendSlackText 경로.

가설: 멘션이 lastActive를 새 스레드로 바꾼 뒤, 웹 런 agent_done이 그 슬롯으로 포워딩되었다. 1677이 멘션 세션(6c6e4301)이 아니라 웹 세션(b25ca0b8)에 적재된 점이 이 가설과 맞다.

원인 분석

목적지가 요청 envelope에 불변 고정되지 않는다.

경로 목적지 확정 시점 소스
Slack origin dispatch (deliverSlackTurnBody) 인입 시 캡처한 target 요청 스코프. 이번 1677 경로 아님
POST /api/channel/send (sendChannelOutput) 전송 시 explicit → turn_conversation → lastActive → latestSeen
agent_done Slack forwarder 전송 시 전역 lastActive만. 이번 건의 주 경로

lastActiveTargets는 프로세스 전역 Map이다 (src/messaging/runtime.ts:91-101). 실행 중 다른 멘션이 오면 슬롯이 바뀌고, 끝나지 않은 웹/CLI 런의 최종본이 새 대화로 새어 나간다. turn_conversation(#474)은 에이전트가 echo할 때만 보호되고, forwarder는 그 값을 보지 않는다.

target-reply-guard가 못 막은 이유

admitTargetReply (src/messaging/target-reply-guard.ts:74-90)는 orchestrate_done 고아 waiter 입학 필터다.

  • type !== orchestrate_done → agent_done은 즉시 null
  • data.origin !== slack → 웹 origin 거부
  • fromQueue|fromSteer|replyViaTarget 없으면 거부

목적지 일치 검사도, lastActive 포워더 차단도 없다. 이번 페이로드는 세 조건 모두 실패해서 가드 밖으로 지나갔고, lastActive forwarder가 그대로 보냈다.

수정 제안 (request-scoped destination binding)

  1. 런 시작 시 destination을 불변 스냅샷으로 고정. SpawnOpts.target / setCurrentMainMeta에 이미 있다. 이후 inbound가 lastActive를 바꿔도 런 스냅샷은 불변.
  2. 모든 agent_done에 그 스냅샷 target을 실어라. lifecycle-handler.ts 전 호출부 (특히 709, 821).
  3. createSlackForwarder는 data.target만 사용. getLastActiveTarget 삭제. target 없는 웹/CLI 런은 Slack에 보내지 말 것 (UI가 목적지). lastActive는 사용자가 지금 보는 방이 아니다.
  4. sendChannelOutput: 에이전트 표면은 allowActiveFallback: false. target/turn_conversation 없으면 실패. echo는 힌트가 아니라 필수 바인딩.
  5. 대안: forwarder를 없애고 Slack origin만 envelope dispatch. 웹 답을 Slack에 미러링하는 기능은 사라지지만 오배송은 줄어든다.

영향 범위

검증

  • 웹 런 실행 중 다른 Slack 스레드 멘션 → 웹 최종본이 그 스레드에 없으면 pass, lastActive로 가면 fail
  • agent_done fixture에 target 없음 → Slack post 0
  • target 있는 런은 인입 후 lastActive가 바뀌어도 원래 thread_ts로만 post
  • 회귀: tests/unit/turn-conversation-routing.test.ts, Slack origin 턴의 자기 스레드 dispatch는 유지

관련 이슈 후보

  • 진행 카드가 새 스레드에 경과 531초로 붙은 현상 (progress lifecycle vs live-run 혼선)
  • pabcd IDLE에서 다른 세션 런이 같은 [jaw:main] 슬롯으로 재개되는 세션 키/스코프 붕괴
  • orchestrateAndCollect가 requestId 없는 orchestrate_done을 새 waiter가 집는 fail-open

같은 인시던트에서 갈라진 이슈: #743 · #744 · #745

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:messagingCross-channel messaging runtime and adaptersarea:slackSlack transport and bot behaviorbugSomething isn't workingpriority:P0Immediate stabilization or correctness work

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions