You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix missing realtime broadcasts for step:started and step:completed events (#305)
## Summary
Fixes critical bug where `step:started` and `step:completed` realtime events were not being broadcast to clients due to PostgreSQL query planner optimizing away unused CTEs.
## Bugs Fixed
### 1. Missing step:started broadcasts (Critical)
**Problem:** Clients never received `step:started` events when steps transitioned from Created to Started status.
**Root Cause:** The `broadcast_events` CTE in `start_ready_steps()` was not referenced by any subsequent CTEs or the final RETURN statement. PostgreSQL's query optimizer eliminated it as "dead code", preventing `realtime.send()` from executing.
**Solution:** Moved `realtime.send()` call directly into the RETURNING clause of the UPDATE statement that marks steps as Started. This ensures the broadcast executes atomically with the state change and cannot be optimized away.
### 2. Missing step:completed broadcasts for empty map steps (Critical)
**Problem:** Clients never received `step:completed` events for map steps that received empty arrays and completed immediately.
**Root Cause:** The `broadcast_empty_completed` CTE in `start_ready_steps()` suffered the same optimization issue - not referenced by subsequent operations.
**Solution:** Moved `realtime.send()` call into the RETURNING clause of the UPDATE that completes empty map steps.
### 3. Missing step:completed broadcasts in cascade completion (Critical)
**Problem:** Clients didn't receive events when taskless steps were cascade-completed in `cascade_complete_taskless_steps()`.
**Root Cause:** Similar CTE optimization issue in the cascade completion logic.
**Solution:** Added `realtime.send()` directly in the RETURNING clause of the cascade completion UPDATE.
## Technical Changes
### SQL Schema Updates
**`start_ready_steps.sql`:**
- Removed `broadcast_events` CTE (step:started)
- Removed `broadcast_empty_completed` CTE (step:completed for empty maps)
- Added `realtime.send()` in RETURNING clause of `started_step_states` CTE
- Added `realtime.send()` in RETURNING clause of `completed_empty_steps` CTE
**`cascade_complete_taskless_steps.sql`:**
- Added `realtime.send()` in RETURNING clause of cascade completion UPDATE
- Broadcasts step:completed events atomically with state transition
**`complete_task.sql`:**
- Added PERFORM statements for run:failed and step:failed event broadcasts
- Ensures error events are reliably broadcast
### Client-Side Improvements
**New `applySnapshot()` methods:**
- Added to `FlowRun` and `FlowStep` classes
- Applies database state directly without emitting events
- Used for initial state hydration from `start_flow_with_states()` and `get_run_with_states()`
**Why this matters:** Previously, we used `updateState()` for initial state, which internally tried to emit events. This was wrong - the initial state from the database is a snapshot, not a series of events. The new `applySnapshot()` method makes this distinction clear.
### Test Updates
Completely rewrote broadcast tests to understand the actual behavior:
**Root steps vs Dependent steps:**
- Root steps start in the same transaction as `start_flow()` - already Started when the function returns
- Can't observe their step:started broadcasts (would require listening before the transaction commits)
- Tests now verify state directly instead of trying to catch impossible broadcasts
**Dependent steps:**
- Start AFTER their dependencies complete (separate transaction)
- CAN observe step:started broadcasts (the actual test case for the bug fix)
- New test specifically verifies these broadcasts work correctly
**Empty map steps:**
- Root empty maps complete in `start_flow()` transaction - verify state directly
- Dependent empty maps complete after dependencies - verify broadcasts
## Testing
All tests passing:
- ✅ Root steps verified via state (already Started when startFlow() returns)
- ✅ Dependent steps receive step:started broadcasts
- ✅ Dependent empty maps receive step:completed broadcasts (skip step:started)
- ✅ Cascade completion broadcasts step:completed events
- ✅ Event sequence validation (started → completed order)
## Migration Notes
No breaking changes. This is a pure bug fix that makes the system work as originally intended.
Users who implemented workarounds for missing events can now remove them.
Fix missing realtime broadcasts for step:started and step:completed events
7
+
8
+
**Critical bug fix:** Clients were not receiving `step:started` events when steps transitioned to Started status, and `step:completed` events for empty map steps and cascade completions were also missing.
9
+
10
+
**Root cause:** PostgreSQL query optimizer was eliminating CTEs containing `realtime.send()` calls because they were not referenced by subsequent operations or the final RETURN statement.
11
+
12
+
**Solution:** Moved `realtime.send()` calls directly into RETURNING clauses of UPDATE statements, ensuring they execute atomically with state changes and cannot be optimized away.
13
+
14
+
**Changes:**
15
+
-`start_ready_steps()`: Broadcasts step:started and step:completed events in RETURNING clauses
16
+
-`cascade_complete_taskless_steps()`: Broadcasts step:completed events atomically with cascade completion
17
+
-`complete_task()`: Added PERFORM statements for run:failed and step:failed broadcasts
18
+
- Client: Added `applySnapshot()` methods to FlowRun and FlowStep for proper initial state hydration without event emission
0 commit comments