Skip to content

fix(ai-gateway): retain streamed upstream responses#3947

Open
marius-kilocode wants to merge 2 commits into
mainfrom
marius/retain-upstream-response-lifetime
Open

fix(ai-gateway): retain streamed upstream responses#3947
marius-kilocode wants to merge 2 commits into
mainfrom
marius/retain-upstream-response-lifetime

Conversation

@marius-kilocode

@marius-kilocode marius-kilocode commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

The proxy returns upstream responses by constructing a new NextResponse around response.body. For fetched responses, runtime cleanup remains associated with the original Response object. Passing only the body to a new response does not transfer that ownership. If the original response becomes unreachable before the downstream reader is acquired, garbage collection can cancel the returned branch even while sibling clones continue consuming the upstream stream.

This is especially hazardous in the shared proxy path, where metrics, usage, and optional request logging create independent tee branches before the original body is handed to Next.js. A sibling branch completing therefore does not protect the client-facing leaf from cleanup tied to the original fetched response.

The response wrapper now uses a lazy identity ReadableStream that keeps the original response strongly reachable for the full lifetime of the returned body:

  • The source reader is acquired only on the first downstream pull, preserving lazy streaming and backpressure.
  • Chunks pass through unchanged without buffering, parsing, or cloning.
  • The original response remains reachable until source EOF, source error, or downstream cancellation.
  • EOF closes the returned stream and releases both the reader lock and retained owner.
  • Source errors propagate after deterministic cleanup.
  • Downstream cancellation propagates before or after the first pull.
  • A cancellation flag prevents a pending pull from closing or erroring a stream that the consumer has already cancelled.
  • Bodyless responses retain a null body, and status, status text, and safe-header behavior remain unchanged.

The fix applies at the common wrapInSafeNextResponse boundary used by streaming model responses and other direct proxy routes. It does not change provider selection, response rewriting, retry behavior, protocol interpretation, or completion semantics.

Comment thread apps/web/src/lib/ai-gateway/llm-proxy-helpers.ts
Comment thread apps/web/src/lib/ai-gateway/wrap-safe-next-response.gc.ts Outdated
Comment thread apps/web/src/lib/ai-gateway/wrap-safe-next-response.gc.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

All 3 previously flagged warnings are resolved: the cancelled flag guards prevent controller.close()/controller.error() from being called on an already-cancelled stream, and both Node.js and undici version pins have been removed from the GC fixture.

Previously Flagged Issues — All Resolved
File Line Issue Status
apps/web/src/lib/ai-gateway/llm-proxy-helpers.ts 317–325 controller.close() / controller.error() could throw on already-cancelled stream ✅ Fixed — if (cancelled) return guards added before both calls
apps/web/src/lib/ai-gateway/wrap-safe-next-response.gc.ts Hard-pinned Node.js version caused GC test to silently skip on version upgrades ✅ Fixed — version gate removed; test now runs unconditionally
apps/web/src/lib/ai-gateway/wrap-safe-next-response.gc.ts Hard-pinned undici version would throw on upgrades causing confusing CI failures ✅ Fixed — undici assertion removed
Files Reviewed (3 files)
  • apps/web/src/lib/ai-gateway/llm-proxy-helpers.ts — 0 issues
  • apps/web/src/lib/ai-gateway/wrap-safe-next-response.gc.ts — 0 issues
  • apps/web/src/lib/ai-gateway/llm-proxy-helpers.test.ts — 0 issues (new cancellation-during-pull test case correctly validates the fix)

Fix these issues in Kilo Cloud


Reviewed by claude-4.6-sonnet-20260217 · 280,524 tokens

Review guidance: REVIEW.md from base branch main

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.

1 participant