Skip to content

fix(cursor): fail over on empty-turn completions instead of fabricating clean 200s - #15725

Merged
diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.52from
patrykkopycinski:fix/cursor-empty-turn-signal
Oct 9, 2026
Merged

diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.52from
patrykkopycinski:fix/cursor-empty-turn-signal

Conversation

@patrykkopycinski

Copy link
Copy Markdown
Contributor

Problem

A cursor-agent turn that decodes zero usable signal (no text, thinking, argumented tool call, or token delta — e.g. only a turn_ended frame) previously finalized into a clean 200 completion. The combo quality gate passes that fabricated empty-success shape, so clients received a successful-looking response with no content they could act on, instead of failing over to another hop.

Truncated tool calls make this worse: under load cursor can emit finish_reason: "tool_calls" with a bare tool name and arguments: ""; treating the bare name as signal let that empty turn finalize cleanly too.

Fix

  • open-sse/executors/cursor.ts: add ctxProducedSignal(ctx) — a shared predicate for whether a StreamCtx saw usable content. A tool call only counts when it carried non-empty arguments. After driveH2 completes, the executor errors the stream (cursor-agent completed turn with no usable content) when no signal was produced, instead of calling finalizeSseStream.
  • open-sse/services/combo/validateQuality.ts: the streaming peek loop now tracks whether any content/terminator was seen; a read error with no prior content is marked invalid so combo fails over, instead of passing through.

Tests

tests/unit/cursor-empty-turn-failover-v9.test.ts:

  • RED: a ctx with only turn_ended reports no produced signal
  • controls: text delta / token-delta-only turns do report signal
  • chain: an executor-erroring empty stream now fails combo validation
  • chain: a healthy stream with real content still passes validation

…ng clean 200s

A cursor-agent turn that decodes zero usable signal (no text, thinking,
argumented tool call, or token delta) previously finalized into a clean
200 completion. The combo quality gate passes that fabricated shape, so
clients received an empty-but-successful response they could not act on.

- ctxProducedSignal: shared predicate for whether a StreamCtx saw usable
  content; a tool call only counts when it carried non-empty arguments
  (cursor truncates tool calls under load to a bare name with empty args).
- The cursor executor errors the stream when driveH2 completes a turn
  with no produced signal, so the peek applies the dead-before-content
  gate and combo fails over.
- validateQuality: track content-produced in the streaming peek loop and
  treat read errors with no prior content/terminator as invalid instead
  of passing through.

Includes unit tests covering the empty-turn red case, healthy controls,
and the peek failover chain.
Wire ctxProducedSignal into the empty-turn check, resume embedded
tool_result XML without an undeclared identifier, and limit the combo
read-error failover to Cursor empty-turn and stream-timeout messages.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw merged commit 84af297 into cheaper-inference:release/v3.8.52 Oct 9, 2026
31 of 49 checks passed
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks @patrykkopycinski — a Cursor turn that only ends with turn_ended, or a tool call whose arguments are blank, now fails over instead of a clean 200. The resume path sends tool results that the translator embedded as user-message XML, and the combo gate only treats Cursor's empty-turn and stream-timeout read errors as hop failures. That commit is on your branch, co-authored. This is in the upcoming release.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants