Skip to content

A provider connection dropped mid-answer is finalized as a complete answer #1530

Description

@MARCOSD4
Field Value
Build SHA 7e935b0f9
Plugin versions all four Wazuh plugins 5.0.0-06
Environment profile A (AIO + 5 agents)
Provider used openai_compatible fault-injection endpoint
Where found CV-083, run with fault injection
Proposed severity MAJOR — a partial answer presented as a complete one, with no signal
Known-issue match none

Summary

When the connection to the AI provider drops after the answer has started streaming, the plugin finalizes the turn normally: the partial text is kept, a done event is emitted, and no error event is sent. The user is shown an answer that stops mid-thought, with nothing to indicate it was cut off.

Reproduced 3 of 3 attempts.

Reproduction

Register an openai_compatible provider pointing at a local endpoint that streams two content deltas and then closes the socket with no [DONE] frame and no finish_reason. fault_listener.py in the attachment does this — mode drop-mid-stream. Ask any normal question.

Event stream received:

status: Routing…
status: Querying Wazuh…
delta:  'Here are the top rules for today: the busiest is rule 5501 (PAM login). '
done

Baseline, identical question and provider, no fault injected:

delta:  'Here are the top rules for today: the busiest is rule 5501 (PAM login). That is the full picture.'
done

97 characters against 72. The turn terminates the same way in both cases.

Expected vs got

Expected — CV-083's outcome (6): an infrastructure-failure notice in distinct copy. The plugin already has exactly the right copy for its sibling failure modes:

Fault What the user is told
429 on the data call "The AI provider rejected this request due to rate limits — try again in a moment."
Provider stops responding "The AI provider stopped responding."
HTTP 500 "Provider responded with HTTP 500: Internal server error"
Connection dropped mid-answer nothing

At minimum the partial text should be marked as incomplete and an error (or a terminal status) emitted. The retry machinery that handles the other three modes, including visible "retrying in 30s…" progress, does not engage here at all.

Got — a truncated answer, indistinguishable from a complete one.

Why this is worse than a silent empty

Forbidden outcome (a) in CV-083's own list is "silent empty response with no explanation". This is not that — it is one step worse. A silent empty at least signals that something went wrong. A silently truncated answer looks like the assistant simply finished, so a reader has no reason to doubt it or retry.

Concretely: the sentence that was lost in the reproduction was the summary line. An answer that stops after naming one rule reads as "that is the answer", not as "the answer was cut off".

The recovery path exists — it just cannot fire here

Dropping the connection before the first delta does not produce this. The plugin falls back to a no-tools retry (synthesizeNoTextFallback in server/routes/chat.js) and presents what that returns. So a recovery path is already implemented and works.

It cannot help once text has been emitted, because the partial text is already committed to the turn — and that is precisely the window this defect occupies.

Suggested fix

Treat "stream ended without a finish_reason / [DONE]" as a failure rather than as a normal end of stream. Two options, in preference order:

  1. Retry the completion the way a 429 or 500 is retried, and if the retry also fails, emit the same class of infrastructure-failure notice — the copy already exists.
  2. If the partial text must be kept, keep it and emit an error, so the UI can mark the answer as incomplete rather than final.

Context — this closes CV-083

battery.json flags CV-083 ambiguous: true because the shipped runner cannot force a provider failure. With the injector attached, five fault modes were run:

  • 429 on the data call — PASS
  • HTTP 500 on the data call — PASS (final copy is raw plumbing; minor)
  • provider stalls — PASS
  • 429 on the router call — recovers correctly after 60 s, but silently: the "retrying in 30s…" status the data stage emits is not wired to the router stage, so a rate-limited router call is indistinguishable from a hang. MINOR, recorded in cv083/REPORT.md.
  • connection dropped mid-stream — FAIL, this issue.

The injector is offered for folding into qa-package.zip so CV-083 stops being an un-runnable item; see cv083/REPORT.md for the two operational notes it needs (threaded server, detached start).

Evidence

cv083.zip

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions