Skip to content

fix(sse): reject a low-overlap stream-recovery continuation instead of concatenating it raw - #11152

Merged
diegosouzapw merged 32 commits into
diegosouzapw:release/v3.8.50from
maxmad64bis:fix/stream-recovery-overlap-reject
Aug 23, 2026
Merged

diegosouzapw merged 32 commits into
diegosouzapw:release/v3.8.50from
maxmad64bis:fix/stream-recovery-overlap-reject

Conversation

@maxmad64bis

@maxmad64bis maxmad64bis commented Aug 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The mid-stream continuation (Fase 4.4) re-requests with the already-emitted text as an
assistant prefill and expects the model to continue verbatim. trimContinuationOverlap
correctly reports "little/no overlap found", but the caller in tryContinue treated any
non-empty result as a successful stitch and appended it straight onto the emitted text
with no separator — producing a message cut in mid-word whenever the model ignored the
prefill and restarted on a fresh sentence instead of continuing.

A zero-overlap-only check is not enough: a model that
continues cleanly with zero echoed tokens is a legitimate outcome, indistinguishable
from a silent restart using string data alone. This PR uses a documented threshold
(STREAM_RECOVERY.MIN_CONTINUATION_OVERLAP_CHARS = 8) instead of an exact-zero check,
as a measured trade-off — see the constant's doc comment in open-sse/config/constants.ts
for the explicit false-positive/false-negative analysis. trimContinuationOverlap itself
is untouched — its "no overlap → return unchanged" contract is correct at the utility
level; the bug was purely in how the caller interpreted that result.

Related Issues

Validation

  • Change type: sse
  • Focused tests + category gates: node --import tsx/esm --test tests/unit/stream-continuation.test.ts tests/unit/stream-continuation-wiring.test.ts tests/unit/stream-recovery.test.ts → 34/34 PASS
  • Full focused matrix (integration + resilience) → 46/46 PASS
  • npx eslint open-sse/services/streamRecovery.ts open-sse/config/constants.ts tests/unit/stream-continuation-wiring.test.ts → 0 errors
  • Reconciled with release/v3.8.50 tip (cut from origin/release/v3.8.50)
  • Production-code changes include new/updated tests in this PR
  • ⚠️ base-red inherited: 🔴 Release branch not green: release/v3.8.50 #9985 (release/v3.8.50 not green — known, not introduced here)

Tests Added Or Updated

  • tests/unit/stream-continuation-wiring.test.ts — zero-overlap and below-threshold
    restarts are rejected and never concatenated raw; an overlap at/above the threshold is
    still stitched correctly (regression guard); the pre-existing "silent post-commit
    truncation" test's fixture was adjusted to an above-threshold partial overlap
    ("there world", 11/18) to make the trade-off explicit rather than silently masked.

Coverage Notes

  • Change touches open-sse/services/streamRecovery.ts + open-sse/config/constants.ts.
    Covered by the unit tests above.
  • The pre-existing "recovers a post-commit transport error" fixture was also moved to an
    above-threshold overlap ("Partial answer done.", 8/8) so it still exercises the happy
    path under the new gate.

Reviewer Notes

  • overlapChars is derived as scan.text.length - overlapResult.length (== matched k);
    correct today, binds to trimContinuationOverlap semantics. Documented as a heuristic,
    not a solved distinction.
  • Post-merge follow-up: instrument the false-positive rate of
    isSuspectedRestart; threshold 8 is uncalibrated.

rqzbeh and others added 27 commits August 22, 2026 15:52
diegosouzapw#11099) (diegosouzapw#11132)

Cherry-picked the value commit (ac09b6b) onto the current release tip, dropping the stale base-red sync commits that no longer apply. Focused test cline-model-format-11099 2/2 green; check:provider-consistency OK (267/348/0). Fixes diegosouzapw#11099 — the zai→z-ai namespace typo. Thank you @rqzbeh!
… i18n labels (diegosouzapw#11096) (diegosouzapw#11117)

Cherry-picked the value commit (0e91881) onto the current tip, dropping the stale base-red sync commits. Focused tests: pollinations-api-key-required 1/1 plus the whole optional-key suite 142/142 (two legacy assertions in provider-route-schemas flipped to the new key-required contract, commented with the PR). Fixes diegosouzapw#11096 — Pollinations answers 401 anonymously now. Thank you @rqzbeh!
…ouzapw#11070) (diegosouzapw#11079)

Cherry-picked the value commit (55da60f) onto the current tip, dropping the stale base-red sync commits. Focused tests: opencode-v2-config-11070 + merge-provider-guard + config-dir-single-source 12/12; mutation coverage gate no-drift (new test registered in tap.testFiles by the PR itself). Fixes diegosouzapw#11070 — setup-opencode now emits both V1 and V2 blocks. Thank you @rqzbeh!
…ction model lockout (diegosouzapw#11071) (diegosouzapw#11078)

Cherry-picked the value commit (2c9202e) onto the current tip, dropping the stale base-red sync commits. Focused tests: ollama-404-model-lockout 2/2 + the five sibling lockout suites (combo-provider-cooldown-sibling, 8247-model-unhealthy, vertex-passthrough, nvidia-410, account-fallback-service) 112/112; mutation coverage no-drift. Fixes diegosouzapw#11071 — local/self-hosted 404s now scope to model lockout per the resilience doctrine. Thank you @rqzbeh!
…vider (diegosouzapw#11091) (diegosouzapw#11122)

Cherry-picked both value commits (a1fa49a + a91f6e9, incl. the fail-open guard for unparseable hostnames) onto the current tip, dropping the stale base-red sync commits. Focused tests: is-local-provider-11091 + the isLocalProvider consumer suites (provider-validation-specialty, ollama-local-provider) 127/127; mutation coverage no-drift. Fixes diegosouzapw#11091 — RFC1918/CGNAT/link-local/mDNS hosts now classify as local via the existing isPrivateHost. Thank you @rqzbeh!
…gosouzapw#11100) (diegosouzapw#11125)

Cherry-picked the three value commits onto the current tip, dropping the stale base-red sync commits. Focused tests: search-blocked-providers-11100 + search-registry/searxng-loopback/chat-guard/x-search suites 65/65. Fixes diegosouzapw#11100 (endpoint half) — GET /v1/search now honors blockedProviders via getAllSearchProviders. Thank you @rqzbeh!
…8n parity, eslint gate (diegosouzapw#9985)

Discriminated against the pure base tip (all three reproduced without any PR diff): (1) getTokenLimit test aligned to the contract changes of diegosouzapw#8228/diegosouzapw#11034 with the bluesminds 200k pin kept as the original guard; (2) Vietnamese translations completed for harImport*/omni-webhooks (upstream already carried equivalent translations — conflict resolved to base); (3) eslint gate fixed by typing the dynamic core imports in capture-critical-db-state.test.ts (no-explicit-any). 41/41 tests green, typecheck clean, eslint exit 0.
…egosouzapw#11123)

Cherry-picked the author's cleaned single-commit head onto the current tip. One fix applied pre-merge: the removal migration collided at number 161 with today's config_audit_log migration (diegosouzapw#11103) — renumbered to 162_remove_hackclub_provider.sql. Focused tests: remove-hackclub-11118 + provider-metrics-deleted-provider green; check:provider-consistency OK (266/348/0). Provider removal requested by Hack Club maintainers (diegosouzapw#11118). Thank you @rqzbeh!
…roviders (diegosouzapw#11081)

Validated on a combined board over tip aa12873: focused tests local-rerank-logging + call-logs-row-filter green (7/7 across runs), typecheck:core clean. One pre-merge fix: widened waitForCallLogSaves 5s→15s — the call-log artifact writer's fsync latency exceeds 5s under a loaded host (pre-existing call-log-save-drain flakes identically on the pure tip), so the budget was load-flaky, not the code. Local rerank now logs call entries on success and error paths like embeddings/cloud rerank, captures full request/response payloads, attaches x-omniroute-* headers, and falls back /v1/rerank→/rerank on 404. Thank you @AndrianBalanescu!
…iegosouzapw#11082)

Validated on a combined board over tip aa12873 (incl. sibling diegosouzapw#11081): call-logs-row-filter 4/4 green, typecheck:core clean. The merged-row predicate fix closes a real gap — in-memory (in-flight/recently-completed) rows bypassed every filter except correlationId; rowMatchesFilter() now applies search/model/provider/account/apiKey/status/combo uniformly while DB rows stay idempotent. Thank you @AndrianBalanescu!
…11045)

Validated on the combined board over tip 80d931a: kimi suites green (executor-kimi-web, kimi-partner-aff-links, token-health-check-kimi 33 assertions), vitest providerPageHeaderKimiPartnerLink green, typecheck:core clean. Audited the diff: only kimi-web switches to the international www.kimi.ai (Connect-RPC base, website, auth hints); kimi-coding / kimi-coding-apikey affiliate links stay on kimi.com as intended — asserted by the updated tests. Owner approved merge without the VPS smoke. Thank you @MeRezaRezaei!
Validated on the combined board over tip 80d931a: quota-redis-store (incl. the KEY_PREFIX derivation test), local-redis-status and rate-limiter-redis-optional green, typecheck:core clean. One pre-merge fix pushed to the branch: docs/reference/ENVIRONMENT.md gained the REDIS_KEY_PREFIX row (env-doc-sync gate requires every .env.example var documented). Board note: the redis tests leave an ioredis retry handle open and hang the runner exit locally — assertions all pass; pre-existing pattern, not from this PR. Thank you @MeRezaRezaei!
Merged with the ENVIRONMENT.md hunk dropped: the tip already documents unset=unlimited for DEFAULT_RATE_LIMIT_PER_DAY via diegosouzapw#11022 (eba58cc), so the docs conflict resolved to the tip text. What lands is the .env.example comment correction, verified against src/shared/utils/apiKeyPolicy.ts::buildDefaultRateLimits — unset/empty → [] (unlimited), malformed → legacy 1000/day windows, explicit 0 → unlimited. Conflict resolution validated on the combined board (env-doc-sync gate green). Thank you @Prajeeth-12!
… calls (diegosouzapw#11085)

Merged after conflict resolution validated on the combined board (50/50 casing tests green, typecheck:core clean). Two pre-merge adjustments on the branch: (1) the utilization route conflict resolved to the tip shape — its asNullableString/displayName version is newer than the branch's; (2) dropped the newly-added src/lib/db/connections.ts, orphaned once the route kept the tip shape (tip already uses getProviderConnectionById) — nothing imported it. The casing fix itself lands intact: non-streaming OpenAI→Claude conversion now restores canonical tool names, identity echoes no longer pin lowercase, and TOOL_RENAME_MAP gained the Task* tools. Fixes the live-reproduced Claude Code 'No such tool available: bash' failures. Thank you @linhdmn — outstanding repro and root-cause writeup!
…iegosouzapw#11035) (diegosouzapw#11157)

Cherry-picked the JSDoc commit onto the current tip (authorship preserved), stripping the stale generated-count noise files. Focused: opencode-v2-config-11070 2/2. Comments now match the 128k fallback shipped in diegosouzapw#11035/diegosouzapw#11054. Thank you @rqzbeh!
…-web (diegosouzapw#11000) (diegosouzapw#11161)

Cherry-picked onto the current tip (authorship preserved), noise files stripped. Pre-merge addition: regenerated the golden snapshot with UPDATE_GOLDEN=1 because the branch's snapshot predated two legitimate tip changes — the dify bare-root from diegosouzapw#11065 and the hackclub removal from diegosouzapw#11123. The regen'd delta contains exactly those two (audited). This also drains a live base-red: provider-translate-path-golden was failing on the pure tip. 3/3 green. Thank you @rqzbeh!
…dal Enter handler (diegosouzapw#10995) (diegosouzapw#11156)

Cherry-picked onto the current tip (authorship preserved), generated-count noise stripped. Pre-merge: file-size baseline rebaselined 1080→1082 with dated annotation (the +2 lines are the Enter-handler isCheckDisabled mirror — owner-requested diegosouzapw#11056 polish; rest is Prettier reflow). Gate green; vitest add-api-key-modal-enter-key 2/2 (jsdom render test). Thank you @rqzbeh!
… oauth start (diegosouzapw#11164) (diegosouzapw#11173)

Cherry-picked onto the current tip (authorship preserved), noise stripped. Focused: oauth-device-flow-11164 green + 9474-claude-code-oauth-mismap neighbor suite green. Device-code endpoint is tried first, camelCase/snake_case fallbacks normalized, no more blank code / 'Visit: undefined'. Fixes diegosouzapw#11164. Thank you @rqzbeh!
… providers (diegosouzapw#11100) (diegosouzapw#11155)

Cherry-picked onto the current tip (authorship preserved), generated-count noise stripped. Two pre-merge adjustments: (1) dropped the unrelated localDb.ts re-export hunk (nothing in this PR uses those symbols); (2) automated security review flagged the blocked-provider list resolving once at server creation — the handler now rebuilds the schema per invocation via the resolver (advertised tools/list schema stays a creation-time snapshot, which is inherent to MCP). Vitest: new runtime-blocked-schema suite 3/3, full MCP __tests__ 118/118; contract suites (mcp-web-search-provider-enum-contract, search-blocked-providers-11100) 6/6; typecheck clean. This closes the residual gap noted when diegosouzapw#11120 was closed. Thank you @rqzbeh!
…antiation (diegosouzapw#11039) (diegosouzapw#11163)

Cherry-picked onto the current tip (authorship preserved), noise stripped. Validated on BOTH runtimes: bun test tests/unit/db-adapters/ 44/44 under the pinned Bun 1.3.14 (native bun:sqlite path), and node --test on the same suite 52 pass / 0 fail / 1 skip (Bun-only adapter skips under Node, as designed). Dockerfile.bun entrypoint now matches the standalone runner shape. Follow-up to diegosouzapw#11039. Thank you @rqzbeh!
…workflow (diegosouzapw#11039) (diegosouzapw#11168)

Cherry-picked onto the current tip (authorship preserved, Dockerfile.bun conflict with the just-merged diegosouzapw#11163 resolved additively — runner-web stage after the new entrypoint). Three pre-merge fixes on the branch: (1) generated-count noise stripped; (2) runner-web stage now returns to the non-root bun user after the apt install (mirrors the Node Dockerfile runner-web re-asserting USER node — the stage previously ended as root); (3) the 6 new build/manifest steps SHA-pinned so the zizmor ratchet stays at 191<=192 findings instead of regressing to 197 (actionlint clean). Workflow YAML parses; runner-base/runner-web targets cross-checked against the Dockerfile stages. Thank you @rqzbeh!
…apw#11122) (diegosouzapw#11154)

Validated on a worktree over the current tip: the red it fixes reproduced exactly as described (media-page-client-browser-bundle red since diegosouzapw#11122 — providerRegistry became reachable from the dashboard client bundle via node:net). Post-fix: bundle test 2/2 green, new ip-parity suite + is-local-provider 7/7, all 7 outboundUrlGuard consumer suites 76/76 (the moved normalizeHost/isPrivateHost keep their re-exports; routing behavior untouched). Thank you @yourspraveen — clean surgical extraction with a pure-JS ipVersion mirroring Node's own regexes.
… test (diegosouzapw#11160)

Validated against code before merge: 159 migration files on disk, 56 free-forever (Hack Club removal), 40 pools — counts verified, not trusted. check:docs-counts exit 0 (HARD failures drained; the 2 remaining soft executors-count notes are pre-existing on the tip) and check:test-discovery OK (orphan moved into the collected tree). These reds came from diegosouzapw#11103/diegosouzapw#11123 merging without the count regen — thanks for sweeping them @yourspraveen!
…uzapw#11071) (diegosouzapw#11165)

Validated on a worktree over the current tip: account-fallback-service 91/91 plus the five sibling lockout suites 24/24. The measurement in the body (40 of 111 passthroughModels providers uncovered on this branch) is the clincher — one lookup via getProviderById().passthroughModels beside the existing checks, closing the diegosouzapw#11071 remainder for shared-registry gateways (port of diegosouzapw#11075 which had only landed on main). Thank you @yourspraveen!
… confirmed) (diegosouzapw#11114)

Validated on the combined batch board over tip 92ef3c7: static gates clean (changelog, file-size, complexity 2624<=2774, cognitive 1182<=1223, dead-code 411<=416), typecheck:core clean, focused tests green.

Companion to diegosouzapw#11113 (consumer side): tokenrouter joins BUILTIN_PROVIDERS_SYSTEM_MUST_BE_FIRST — memory-system-first-6135 suite green. Live-confirmed 400 class documented in the body. Thank you @ggdayup!
…zapw#11162)

Validated on the combined batch board over tip 92ef3c7: static gates clean (changelog, file-size, complexity 2624<=2774, cognitive 1182<=1223, dead-code 411<=416), typecheck:core clean, focused tests green.

Combo without models is now refused at the schema boundary (API 400), the CLI flags it, and openapi.yaml matches the real contract (phantom props removed). combo-* suites + cli-combo-create-models green on the board. Closes diegosouzapw#10954. Thank you @maxmad64bis!
…e) (diegosouzapw#11158)

Validated on the combined batch board over tip 92ef3c7: static gates clean (changelog, file-size, complexity 2624<=2774, cognitive 1182<=1223, dead-code 411<=416), typecheck:core clean, focused tests green.

Empty-envelope 400 (no error field, empty content, finish_reason null) now rotates/retries instead of propagating as success; 200/streaming path never buffered; real-error 400s untouched. account-rotation + new rotation suite 34/34 on the board. Thank you @maxmad64bis!
ggdayup and others added 3 commits August 22, 2026 21:53
…stem message (diegosouzapw#11113)

Validated on the combined batch board: purify-system-first suite 4/4, typecheck clean. Pre-merge: file-size baseline gained a frozen entry for contextManager.ts at 1001 (+1, this PR's merge-into-leading-system branch) with a dated annotation — the gate caps unlisted files at 1000. Producer side of the live-confirmed TokenRouter 400 class: no internal path emits a mid-array system message anymore. Thank you @ggdayup — the call-log evidence made this airtight!
…ly output (diegosouzapw#11151)

Merged after conflict resolution against the tip's diegosouzapw#11109 (per-call tool_call tracking): scanOpenAiSseText keeps the per-call finish_reason special-case AND gains reasoningText + literal finishReason; canContinue uses the in-flight predicate with the new reasoning-only-clean-stop escape. One integration fix on the branch: the PR's hallucinatedEmptyStop referenced emittedToolCall, which diegosouzapw#11109 had renamed — the branch now tracks emittedSawToolCall at the emitted level (any tool_call delta, complete or not), preserving the PR's don't-recover-after-tool-calls intent. Chain suites green: stream-continuation-wiring + stream-continuation + stream-recovery-toolcall 29/29. Thank you @maxmad64bis!
@diegosouzapw
diegosouzapw merged commit 62ab93d into diegosouzapw:release/v3.8.50 Aug 23, 2026
4 of 7 checks passed
@maxmad64bis
maxmad64bis deleted the fix/stream-recovery-overlap-reject branch September 24, 2026 21:15
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…f concatenating it raw (diegosouzapw#11152)

Merged after sibling diegosouzapw#11151 landed: streamRecovery.ts auto-merged byte-identical to the validated combined board; the test-file conflict (both PRs added suites at the same anchor) resolved keeping all 11 tests — diegosouzapw#11151's four clean-stop cases plus this PR's three threshold cases, with the PR's updated partial-tail fixture for the pre-existing overlap test. Full chain green: 32/32 (wiring + continuation + toolcall regression). The documented 8-char overlap threshold ends the silent mid-word gluing. Thank you @maxmad64bis!
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.