Bugfix: Why does module-gen.completed SSE event sometimes never reac...

Documentation for Sessions

🐛 Bugfix: Why does module-gen.completed SSE event sometimes never reac...

Date: 2026-03-19
Time: 11:55:02 UTC
Session: gspace1-236.2
Agent: backend
Confidence: 95%
Status: ✅ Documented


📋 Decision Context

Question:

Why does module-gen.completed SSE event sometimes never reach the frontend, causing the preview to stay blank?

Task: S236: Fix platform generation pipeline — live preview never renders after generation


🎯 Decision Made

Chosen:

Move emit('module-gen.completed') BEFORE session.status = 'completed' in the phase executor. The event must fire while the plugin subscription is still alive.

Options Considered:

  1. Move emit before status change (chosen — eliminates race entirely)
  2. Add a delay before unsubscribing in the plugin (fragile — depends on timing)
  3. Use a separate completion signal instead of status polling (over-engineered)
  4. Buffer the last N events in the SSE subject (complex, doesn't fix root cause)

💭 Reasoning

Root cause: In module-gen-phase.executor.ts, the sequence was: (1) session.status = 'completed' (line 869), (2) await checkpoint_complete() (line 881, YIELDS to event loop), (3) emit('module-gen.completed') (line 885). Meanwhile, module-gen.plugin.ts has waitForCompletion() polling every 5s. During the await on checkpoint_complete(), the poller detects status==='completed', resolves, and runs pipelineSub.unsubscribe() — killing SSE forwarding BEFORE the completed event is emitted. This is a classic async race condition: the status change and event emission are separated by an await, creating a window for the poller to steal the subscription. Fix: emit events first (while status is still 'running'), then set status to 'completed'. Events are guaranteed to be forwarded before the poller can react.


📁 Files Involved


🔗 Related


Auto-generated by MCP Smart Consolidation