mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
The agentic loop has a race between the main goroutine and the `Start`
goroutine on the shared `ResponseWriter`. When an iteration's response
contains only injected-tool events (no text to relay), `Start` may not
have called `InitiateStream` by the time main reaches the `IsStreaming`
check on the next iteration. The `IsStreaming` check then returns false,
main writes a JSON error via `writeUpstreamError`, and `Start` later
writes SSE headers and events on top, producing a malformed JSON+SSE
response:
```
{\"error\":{\"message\":\"all configured keys are rate-limited\",\"type\":\"rate_limit_error\"},\"request_id\":\"\",\"type\":\"error\"}event: message_start\n..."
```
Fix: explicitly call `events.InitiateStream(w)` at the agentic
continuation point so the SSE stream is committed before the next
iteration runs. Keeps `messages` consistent with the pattern already
used in `chatcompletions/streaming.go`. `sync.Once` makes the double
call safe.
Related: coder/internal#1524
Related: coder/coder#25654
Closes:
https://linear.app/codercom/issue/AIGOV-336/flake-teststreaminginterception-agenticloopfailoveragentic-all-keys
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by
@ssncferreira
These fixtures were created by adding logging middleware to API calls to view the raw requests/responses.
...
opts = append(opts, option.WithMiddleware(LoggingMiddleware))
...
func LoggingMiddleware(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) {
reqOut, _ := httputil.DumpRequest(req, true)
// Forward the request to the next handler
res, err = next(req)
fmt.Printf("[req] %s\n", reqOut)
// Handle stuff after the request
if err != nil {
return res, err
}
respOut, _ := httputil.DumpResponse(res, true)
fmt.Printf("[resp] %s\n", respOut)
return res, err
}