Skip to content

fix(relay): gzip large PS responses so big exports survive the relay tunnel (BUI-591)#166

Merged
maciejwitowski merged 1 commit into
devfrom
maciej/bui-591-ps-gzip-large-exports-dev
Jun 25, 2026
Merged

fix(relay): gzip large PS responses so big exports survive the relay tunnel (BUI-591)#166
maciejwitowski merged 1 commit into
devfrom
maciej/bui-591-ps-gzip-large-exports-dev

Conversation

@maciejwitowski

Copy link
Copy Markdown
Contributor

Re-lands the BUI-591 gzip fix on dev — the branch the deployed surfaces actually consume (unity-surfaces vendors compiled dist/ from dev for app-dev.vana.org). The original PR #164 merged into main, which has diverged from dev, so it could not reach app-dev; #165 reverts it from main.

Clean cherry-pick of bfa9225 (relay.ts is byte-identical on both branches at the base). Verified on dev: npm run build clean, full packages/lite suite passes (121 tests, incl. the two BUI-591 relay cases).

What it does

Large exports (e.g. chatgpt.conversations) are big JSON streamed over the relay's WebSocket tunnel; an uncompressed body inflates transfer time and the window for a mid-stream drop (truncated read → UND_ERR_RES_CONTENT_LENGTH_MISMATCH). When the client sends Accept-Encoding: gzip (undici does, automatically, and auto-decompresses) and the body is large enough, the relay gzips the response and sets content-encoding: gzip. JSON shrinks ~5–10× → transfer finishes inside the reliable window small scopes already enjoy.

Correctness (verified)

  • gzip sits between the PS response (incl. any app-layer encryption) and TLS — transparent transport layer; undici decompresses before the app decrypts the identical bytes. Read path returns plaintext JSON, so the fix is also effective, not just safe.
  • undici round-trip confirmed empirically (advertises gzip + auto-decompresses, both SDK res.json() and the resilient arrayBuffer() paths).
  • Strips any upstream content-length before serializing so the gzip response can't carry two conflicting lengths (Codex review catch from fix(relay): gzip large PS responses so big exports survive the relay tunnel (BUI-591) #164).

Guards

Only fires when the client accepts gzip, skips already-encoded bodies, no-ops where CompressionStream is absent, ≥1KB threshold.

Closes BUI-591.

…tunnel (BUI-591) (#164)

## Problem
Large exports (e.g. `chatgpt.conversations`) are big JSON streamed
**uncompressed** over the relay's WebSocket tunnel. The longer the
transfer, the wider the window for a mid-stream drop, which the
SDK/client sees as a truncated read
(`UND_ERR_RES_CONTENT_LENGTH_MISMATCH`). Small scopes (e.g.
`instagram.profile`) finish inside the reliable window and already work.
Full analysis in **BUI-591** — this is fix #1 of the prioritized minimal
set.

## Change
gzip the response body in the relay's HTTP serialization when: the
client sent `Accept-Encoding: gzip` (undici + browsers do, and
auto-decompress — transparent to builders), the body is large enough to
be worth it (`>= 1KB`), and it isn't already encoded. JSON typically
shrinks 5–10×, bringing large exports into the same reliable window as
small scopes (fewer WS frames, faster transfer, less browser memory).

Implementation:
- `buildHttpResponse` stays **sync** (existing callers/tests unaffected)
via an optional `bodyOverride` + `contentEncoding`; the async gzip runs
in `handleRelayHttpRequest`.
- Sets `content-encoding: gzip` and a `content-length` matching the
compressed body.

## Verification
- `npm run build` clean (tsc --build, all packages).
- `packages/lite` tests: **114 passed**, incl. the existing
binary-safety regression and a new test for the override /
content-encoding path.

## Notes
- Complements the existing 16KB response chunking (compression cuts
total bytes/frames; chunking bounds frame size).
- Addresses the **large-payload truncation** axis of BUI-591. The
separate **availability** axis (browser PS flapping / TLS resets when
the tab isn't tunneled) is out of scope here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maciejwitowski maciejwitowski merged commit 90b3a80 into dev Jun 25, 2026
3 checks passed
@maciejwitowski maciejwitowski deleted the maciej/bui-591-ps-gzip-large-exports-dev branch June 25, 2026 12:23
@github-actions

Copy link
Copy Markdown

Codex Review

Findings

  • packages/lite/src/relay.ts:388 Accept-Encoding is parsed with a substring check, so Accept-Encoding: gzip;q=0 or similar still triggers gzip. q=0 explicitly means gzip is not acceptable, so clients that opt out can receive a response they will not decode. Parse comma-separated codings case-insensitively and honor q values, then add a relay-path test for gzip;q=0.

Verification

  • Tried npm test -- packages/lite/src/relay.test.ts, but it could not run because vitest is not installed in this workspace (sh: 1: vitest: not found).

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