@@ -122,11 +122,12 @@ func postAgent(t *testing.T, c *controllerV1, ctx context.Context, wsID, session
122122}
123123
124124// TestPostAgent_ReturnsOKOnContextCanceled verifies that when another
125−// client cancels the session mid-turn, the prompting client's still
126−// open POST receives 200 (not 500). The agent surfaces the
127−// FinishReasonCanceled marker to every SSE subscriber via the
128−// assistant message; the HTTP response from the prompter should not
129−// double as an error signal.
125+// client cancels the session mid-turn, the prompting client's POST is
126+// unaffected: SendMessage is fire-and-forget, so the handler returns
127+// 200 immediately without waiting for the turn. A run that later
128+// returns context.Canceled never surfaces as a 500 to the prompter;
129+// the FinishReasonCanceled marker reaches SSE subscribers via the
130+// assistant message instead.
130131func TestPostAgent_ReturnsOKOnContextCanceled(t *testing.T) {
131132 t.Parallel()
132133
@@ -135,33 +136,26 @@ func TestPostAgent_ReturnsOKOnContextCanceled(t *testing.T) {
135136 })
136137 c, wsID := buildAgentWorkspace(t, coord)
137138
138− done := make(chan *httptest.ResponseRecorder, 1)
139− go func() {
140− done <- postAgent(t, c, t.Context(), wsID, "S1")
141− }()
139+ // The handler returns immediately, before the dispatched run is
140+ // released, because the run no longer owns the HTTP response.
141+ rec := postAgent(t, c, t.Context(), wsID, "S1")
142+ require.Equal(t, http.StatusOK, rec.Code, "fire-and-forget SendMessage must return 200 without waiting for the run")
142143
143− // Wait until Run is in flight, then release it to return
144− // context.Canceled.
144+ // The run is dispatched on a goroutine; let it return
145+ // context.Canceled. Nothing from that path reaches the (already
146+ // returned) handler.
145147 select {
146148 case <-coord.entered:
147149 case <-time.After(2 * time.Second):
148− t.Fatal("coordinator Run was never entered")
150+ t.Fatal("dispatched run was never entered")
149151 }
150152 close(coord.release)
151−
152− select {
153− case rec := <-done:
154− require.Equal(t, http.StatusOK, rec.Code, "context.Canceled from another client's cancel must not surface as 500")
155− case <-time.After(2 * time.Second):
156− t.Fatal("handler did not return after coordinator returned context.Canceled")
157− }
158153}
159154
160−// TestPostAgent_DetachesRequestContext verifies that canceling the
161−// prompting client's HTTP request context does not cancel the
162−// in-flight agent run. The coordinator must observe a context whose
163−// Done channel never fires from the request side; only the explicit
164−// cancel endpoint may end the run.
155+// TestPostAgent_DetachesRequestContext verifies that the dispatched run
156+// is bound to the workspace context, not the prompting client's HTTP
157+// request context. Canceling the request context must neither cancel
158+// the run nor be observed by the coordinator.
165159func TestPostAgent_DetachesRequestContext(t *testing.T) {
166160 t.Parallel()
167161
@@ -172,46 +166,31 @@ func TestPostAgent_DetachesRequestContext(t *testing.T) {
172166
173167 reqCtx, cancelReq := context.WithCancel(context.Background())
174168
175− done := make(chan *httptest.ResponseRecorder, 1)
176− go func() {
177− done <- postAgent(t, c, reqCtx, wsID, "S1")
178− }()
169+ // The handler returns immediately; the run keeps executing on its
170+ // own goroutine bound to the workspace context.
171+ rec := postAgent(t, c, reqCtx, wsID, "S1")
172+ require.Equal(t, http.StatusOK, rec.Code)
179173
180− // Wait until Run is in flight, then drop the prompting client.
181174 select {
182175 case <-coord.entered:
183176 case <-time.After(2 * time.Second):
184− t.Fatal("coordinator Run was never entered")
177+ t.Fatal("dispatched run was never entered")
185178 }
179+
180+ // Drop the prompting client. This must not reach the run.
186181 cancelReq()
187182
188− // The captured ctx must be detached: context.WithoutCancel
189− // returns a ctx with Done() == nil so request cancellation cannot
190− // propagate.
191183 got := coord.capturedCtx()
192184 require.NotNil(t, got)
193− require.Nil(t, got.Done(), "coordinator ctx must be detached from r.Context() via context.WithoutCancel")
194− require.NoError(t, got.Err(), "coordinator ctx must not inherit cancellation from the dropped request")
185+ // Compare by identity (pointer), not reflect.DeepEqual: deep
186+ // comparison would traverse context internals that the runtime
187+ // mutates concurrently.
188+ require.False(t, got == reqCtx, "run ctx must not be the request ctx")
189+ require.NoError(t, got.Err(), "run ctx must not inherit cancellation from the dropped request")
195190
196− // Confirm Run is still running: it should not have completed
197− // just because the request ctx was canceled.
198− select {
199− case <-done:
200− t.Fatal("handler returned before run completed; request ctx cancellation leaked into the run")
201− case <-time.After(50 * time.Millisecond):
202− }
203−
204− // Release the run; the handler should now complete cleanly.
191+ // Release the run so it returns cleanly.
205192 close(coord.release)
206− select {
207− case rec := <-done:
208− // Writing to a recorder whose request ctx was canceled
209− // still works; in production the TCP write would silently
210− // fail, which is fine because the run already completed and
211− // SSE subscribers have the result.
212− require.Equal(t, http.StatusOK, rec.Code)
213− case <-time.After(2 * time.Second):
214− t.Fatal("handler did not return after release")
215− }
216− require.Equal(t, int32(1), coord.ranCount.Load())
193+ require.Eventually(t, func() bool {
194+ return coord.ranCount.Load() == 1
195+ }, 2*time.Second, 10*time.Millisecond)
217196}