ITADN

Chat UI stops updating mid-turn and thinking indicator stays stuck

#1915Openystknsh 创建于 2026-07-01
Y
ystknshcommented
## Summary エージェントのターン中に socket.io イベントの配信が途切れ、「考え中…」インジケーターが消えずにスタックする。セッションは裏で正常に完了しているが、ページをリロードしないと結果が表示されない。 <img width="1876" height="666" alt="Image" src="https://github.com/user-attachments/assets/d1518045-dd51-4a18-ba08-dfb49179aa30" /> 環境 - M4 Mac BookAir - Safari ## 症状 エージェントのターン中に、ある時点から後続のイベント(tool_call、text、session_finished すべて)がブラウザに届かなくなる。 - セッションは裏で正常に動作・完了している - 「考え中…」インジケーターが表示されたまま消えない - ページをリロードすると jsonl から読み直されて正しく表示される ## 再現状況(7/1 確認) `/mulmo-beta-feedback` スキルで Slack 読み取り → コレクション更新中に発生。 1. アシスタントの text イベント(「了解です。コレクションのレコードに issue 案のフィールドを書き込むだけですね。」)までは正常に表示 2. 直後の ToolSearch → manageCollection → 完了テキスト が一切表示されない 3. セッションは完了済み(jsonl に完了報告まで記録されている) 4. リロードで全内容が表示された ## 原因分析 2つの問題が重なっている。 ### 1. socket.io 接続の瞬断でイベントが丸ごと消失する ターン途中で socket.io 接続が瞬断すると、その間のイベントがすべて失われる。socket.io は再接続時にチャンネルを再登録するが、見逃したイベントの再送は行わない。`session_finished` も含めて消失するため、セッション完了を検知できない。 関連コード: `src/composables/usePubSub.ts` — `resendSubscriptions` on reconnect, but no mechanism to catch up on missed events. ### 2. クライアントに「考え中」を直接解除する手段がない `handleSessionFinished()` は `session.isRunning = false` を直接セットしない(PR #192 で削除)。代わりに `sessions` チャンネルの pub/sub 通知 → `refreshSessionStates()` → サーバー API から差分取得、という間接経路に依存しているが、この通知自体も瞬断で消失する。 さらに `refreshSessionStates` はカーソルベースの差分フェッチ(`changeMs > cursor`)を使い、`changeMs` はディスク上のファイル mtime で判定される。`endRun()` 内の `persistHasUnread` が await されず fire-and-forget のため、ディスク書き込み完了前にクライアントの fetch が到着すると、mtime 未変更 → カーソルフィルタで除外 → 古い `isRunning: true` がそのまま使われる。 関連コード: - `server/events/session-store/index.ts` L178-191 — `endRun()`: `persistHasUnread` が await されていない - `src/composables/useSessionSync.ts` L33-53 — `refreshSessionStates()`: diff に含まれないセッションは古い値を使い続ける - `src/composables/useSessionHistory.ts` L38-56 — `fetchSessions()`: カーソルベースの差分フェッチ ## 修正案 | 対策 | 内容 | 効果 | |---|---|---| | **A. `handleSessionFinished` で即座に `isRunning = false`** | PR #192 で削除されたコードの復元 | `session_finished` が届けば確実に解除。瞬断で `session_finished` 自体を見逃した場合は効果なし | | **B. socket.io 再接続時にトランスクリプトを再取得** | `reconnect` イベントで `refreshSessionTranscript` + `refreshSessionStates` を呼ぶ | 瞬断中に見逃したイベントをリロードなしで回復 | | **C. `endRun` で `persistHasUnread` を await してから通知** | ディスク書き込み完了後に `notifySessionsChanged` | カーソル差分の race condition を解消 | A + B の組み合わせが最も確実。C は差分フェッチの信頼性向上として補助的に有効。
3 条评论