Tell a subscribing client whether the thread's turn is still running - #155
Conversation
A thread's first turn is started over HTTP, before the browser has a socket for that thread, so the composer locks on nothing more than the knowledge that a start request was sent. Every other lock is released by something the socket delivers, but this one has no such release when the turn finishes before the socket attaches: the TurnCompleted was broadcast to a thread nobody was subscribed to, and a turn that is over leaves no LiveTurnSnapshot behind. Against a fast harness that is the common case, not the rare one, and it left the composer disabled — Stop showing, Send gone — with the agent's reply already on screen, until the thread was re-opened from the sidebar. The subscribe snapshot now states turn liveness outright. ThreadState carries active_turn, answered from the active-turn gate rather than the live buffer: the gate is held from before the start request returns until the turn ends, so it also covers the window where the harness has accepted a turn but not yet streamed anything and the buffer is still empty. A false releases the optimistic lock; anything else leaves it alone, so a live turn still holds the composer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEjHUhgZUTTwPtYKjA7YN2
📝 WalkthroughWalkthroughChangesActive turn synchronization
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant WebSocketSubscription
participant TurnRegistry
participant Composer
Client->>WebSocketSubscription: subscribe to thread
WebSocketSubscription->>TurnRegistry: register client and read active_turn
TurnRegistry-->>WebSocketSubscription: live turn status
WebSocketSubscription-->>Client: ThreadState with active_turn
Client->>Composer: render state and release lock if idle
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/giskard-server/src/routes.rs (1)
3391-3430: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winClean up failed WebSocket subscriptions.
If subscription bootstrap fails after
state.hub.subscribe(...), callstate.hub.unsubscribe(thread_id, client_id).awaitbefore returning the error.handle_wssends the error and continues; it only removes subscriptions when the socket closes. Add focused failure-path coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/giskard-server/src/routes.rs` around lines 3391 - 3430, Update the WebSocket subscription bootstrap surrounding state.hub.subscribe and the subsequent recompute_aggregates, serialization, and ThreadState send operations so any failure after subscribing first awaits state.hub.unsubscribe(thread_id, client_id), then propagates the original error. Preserve normal successful subscriptions and add focused coverage verifying cleanup when bootstrap fails.Sources: Coding guidelines, Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@crates/giskard-server/src/routes.rs`:
- Around line 3391-3430: Update the WebSocket subscription bootstrap surrounding
state.hub.subscribe and the subsequent recompute_aggregates, serialization, and
ThreadState send operations so any failure after subscribing first awaits
state.hub.unsubscribe(thread_id, client_id), then propagates the original error.
Preserve normal successful subscriptions and add focused coverage verifying
cleanup when bootstrap fails.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 24256c94-eb49-42af-b13c-1544261dc6f1
📒 Files selected for processing (7)
crates/giskard-proto/src/lib.rscrates/giskard-server/src/routes.rscrates/giskard-server/static/app.jscrates/giskard-server/tests/e2e_smoke.rscrates/giskard-server/tests/ui.rsspecs/giskard-specification.mdtests/e2e/tests/draft-composer.spec.ts
|
Skipping the "clean up failed WebSocket subscriptions" finding, with reasoning. Nothing accumulates: the browser sends What is left is a choice of failure semantics, and unsubscribing makes the user-visible outcome worse. After a bootstrap failure It is also pre-existing rather than introduced here: The real improvement is probably to close the socket on a failed subscribe so the client reconnects and retries, which is a separate change with its own failure-path tests rather than something to fold into this one. Generated by Claude Code |
A thread's first turn is started over HTTP, before the browser has a
socket for that thread, so the composer locks on nothing more than the
knowledge that a start request was sent. Every other lock is released by
something the socket delivers, but this one has no such release when the
turn finishes before the socket attaches: the TurnCompleted was broadcast
to a thread nobody was subscribed to, and a turn that is over leaves no
LiveTurnSnapshot behind. Against a fast harness that is the common case,
not the rare one, and it left the composer disabled — Stop showing, Send
gone — with the agent's reply already on screen, until the thread was
re-opened from the sidebar.
The subscribe snapshot now states turn liveness outright. ThreadState
carries active_turn, answered from the active-turn gate rather than the
live buffer: the gate is held from before the start request returns until
the turn ends, so it also covers the window where the harness has
accepted a turn but not yet streamed anything and the buffer is still
empty. A false releases the optimistic lock; anything else leaves it
alone, so a live turn still holds the composer.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01DEjHUhgZUTTwPtYKjA7YN2
Summary by CodeRabbit
Bug Fixes
Tests
Documentation