Summary
POST /threat_models/{threat_model_id}/chat/sessions reports a synchronous, pre-stream failure as
HTTP 200 with the error delivered inside the SSE body:
HTTP/1.1 200 OK
Content-Type: text/event-stream
event: error
data: {"code":"session_creation_failed","message":"threat model has reached the maximum of 50 active sessions"}
Nothing was streamed before the error — the request was rejected outright. A caller checking the
status code sees success.
Observed in CATS run 20260730T220551Z (tests 55707, 55710, 55711, 55717, 55745, 55746, 55791,
55792, 55839, 55983, 55984 and others).
Why it matters
- A client cannot distinguish "session created, stream follows" from "request refused" without
parsing the stream, which defeats ordinary HTTP error handling, retry policy and middleware.
- The session cap is a resource-exhaustion condition with an established status code (429, or 409
if treated as a conflict). Both are already documented for this operation.
- SSE's usual justification does not apply here: headers only have to be committed before the first
event is written. This failure is known before any event, so the status can still be set.
Suggested fix
Perform session-creation validation (cap check, authorization, diagram existence) before
writing SSE headers, and return a normal JSON error response with 429/409/400 as appropriate. Only
switch to text/event-stream once creation has succeeded and the stream is genuinely starting.
Errors that arise mid-stream should keep using event: error, which is correct once headers are
committed.
Secondary: the cap is reached by the campaign itself
Sessions accumulate across a campaign until the 50-session ceiling is hit, after which every
remaining chat/sessions test exercises only the rejection path. That is a coverage loss of the
same family as #651 and #633. Either the sessions need a shorter inactivity timeout for fuzzing, or
the seed/teardown hook should reap them, or the path should be fuzzed scoped.
Related: #651 (anchor decoy coverage loss), #633 (DELETE /me coverage loss).
Summary
POST /threat_models/{threat_model_id}/chat/sessionsreports a synchronous, pre-stream failure asHTTP 200 with the error delivered inside the SSE body:
Nothing was streamed before the error — the request was rejected outright. A caller checking the
status code sees success.
Observed in CATS run
20260730T220551Z(tests 55707, 55710, 55711, 55717, 55745, 55746, 55791,55792, 55839, 55983, 55984 and others).
Why it matters
parsing the stream, which defeats ordinary HTTP error handling, retry policy and middleware.
if treated as a conflict). Both are already documented for this operation.
event is written. This failure is known before any event, so the status can still be set.
Suggested fix
Perform session-creation validation (cap check, authorization, diagram existence) before
writing SSE headers, and return a normal JSON error response with 429/409/400 as appropriate. Only
switch to
text/event-streamonce creation has succeeded and the stream is genuinely starting.Errors that arise mid-stream should keep using
event: error, which is correct once headers arecommitted.
Secondary: the cap is reached by the campaign itself
Sessions accumulate across a campaign until the 50-session ceiling is hit, after which every
remaining
chat/sessionstest exercises only the rejection path. That is a coverage loss of thesame family as #651 and #633. Either the sessions need a shorter inactivity timeout for fuzzing, or
the seed/teardown hook should reap them, or the path should be fuzzed scoped.
Related: #651 (anchor decoy coverage loss), #633 (
DELETE /mecoverage loss).