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: address review findings from CodeRabbit + Copilot
Six real bugs + four hardening + two trivial fixes. Test coverage added
for every behavioral change.
Real bugs:
1. Cancelled background jobs were flipped to COMPLETED/FAILED.
_buffer_background_run's finally block wrote a terminal status
unconditionally; if stop_workflow set CANCELLED in parallel, the
finally overwrote it. Added _finalize_job_status which re-reads the
row and skips the update when the job is already CANCELLED.
2. Background scheduling failures left orphan buffers. If create_queue
or start_job raised after _register_background_run, the buffer
lingered with done=False forever. Wrapped scheduling in try/except,
call _clear_background_run + finalize FAILED on the failure path.
3. Stop didn't abort the v2 SSE request. flowStore.stopBuilding aborts
buildController, but buildFlow called runFlowAGUI without a signal.
Threaded the controller's signal through runFlowAGUI and linked it
to the agent's AbortController so Stop cancels the in-flight fetch.
4. runFlowAGUI set buildInfo only on RUN_FINISHED/RUN_ERROR. If the
observable's complete fired without a terminal event (truncated
stream, proxy timeout), buildInfo stayed null and trackFlowBuild
read the run as success. Now sets a default failure buildInfo on
silent complete and on abort.
5. useRunFlow's run() Promise never settled on abort. abort() and
preempting run() left awaiters deadlocked because RxJS doesn't fire
error/complete for those paths. Track resolver in a ref, settle on
abort + preemption.
6. AGUITranslator emitted TextMessage* events with message_id="".
token and add_message dropped events when the upstream payload
omitted id, but still emitted START/CONTENT/END for the empty id,
producing malformed AG-UI streams. Guard the emission paths.
Hardening:
7. state.ts shape validation: STATE_SNAPSHOT with nodes=null or nodes
as array, STATE_DELTA with non-array delta — all treated as no-ops
instead of crashing the reducer.
8. _BackgroundRun.frames bounded by _MAX_FRAMES_PER_BACKGROUND_RUN
(10k). Token-streaming flows used to grow without bound;
_MAX_BACKGROUND_RUNS only capped the number of buffers. Oldest
frames are evicted; replay tracks a base_index so re-attach with
Last-Event-ID past the evicted head starts from the new head.
9. _stream_event_frames inline queue bounded by _EVENT_QUEUE_MAX_SIZE
(256). Slow consumer applies backpressure to the build loop
instead of letting the queue grow unbounded.
10. applyStateDelta toggles edge animation in step with node status:
running -> on, success/error -> off. Mirrors the v1 build path so
edges animate during the run, not just clear at the end.
Trivial test tightening:
11. Last-Event-ID assertion in test_workflow_agui.py is CRLF-agnostic
(split per line + check the id list).
12. Three weak "assert str(job_id) in result[...]" checks in
test_workflow.py changed to exact equality.
0 commit comments