Skip to content

emrg: LLM client tolerates gzip response bodies without Content-Encoding - #541

Merged
argszero merged 1 commit into
masterfrom
fix/llm-gzip-body-tolerance
Aug 7, 2026
Merged

emrg: LLM client tolerates gzip response bodies without Content-Encoding#541
argszero merged 1 commit into
masterfrom
fix/llm-gzip-body-tolerance

Conversation

@argszero

@argszero argszero commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Problem

Some API gateways/proxies return gzip-compressed response bodies without a proper Content-Encoding header, so httpx does not decompress them and resp.json() crashes with UnicodeDecodeError on the gzip magic bytes (0x1f 0x8b).

Observed in production (2026-08-07 12:40, emrgd.log):

memory reflection failed
  File "emrg/server/llm.py", line 134, in chat
    data = resp.json()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte

The reflection died outright — no retry, no recovery.

Fix

  1. _parse_json_body() — new helper: detects the gzip magic prefix (0x1f 0x8b) and decompresses transparently before json.loads.
  2. chat() — catches JSONDecodeError/UnicodeDecodeError/OSError/EOFError on the 200 body and retries with exponential backoff (same policy as transient HTTP errors) instead of crashing. After MAX_RETRIES it raises a clean RuntimeError.
  3. +6 tests: plain/gzip/corrupt-gzip parsing, transparent decompress (no retry), retry-then-succeed, retry exhaustion.
  4. Doc test counts synced 502 → 508 (README.md / README.cn.md / Agent.md).

Verification

  • uv run pytest tests/ — 508 passed
  • from emrg.client.app import run_client — OK
  • python -m emrg --help — OK

Triggered by a real daemon crash, not speculation.

Some API gateways/proxies return gzip-compressed response bodies without
a proper Content-Encoding header, so httpx does not decompress them and
resp.json() crashes with UnicodeDecodeError on the gzip magic bytes
(0x1f 0x8b).

Observed in production at 2026-08-07 12:40: memory reflection failed with
UnicodeDecodeError at llm.py:134 — the reflection died outright with no
retry.

Fix:
- _parse_json_body(): detect gzip magic prefix and decompress transparently
- chat(): catch JSONDecodeError/UnicodeDecodeError/OSError/EOFError on the
  200 body and retry with exponential backoff instead of crashing
- +6 tests: plain/gzip/corrupt-gzip parsing, transparent decompress,
  retry-then-succeed, retry exhaustion
- doc counts synced 502 -> 508 (README/README.cn/Agent.md)

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle

Reviewed the full diff on branch fix/llm-gzip-body-tolerance (954349a):

  1. _parse_json_body() — correct gzip magic-byte detection (0x1f 0x8b) with transparent decompress; pure function, no side effects.
  2. chat() — unparseable 200 bodies now retry with the same exponential-backoff policy as transient HTTP errors instead of crashing; clean RuntimeError after MAX_RETRIES. Exception set (JSONDecodeError/UnicodeDecodeError/OSError/EOFError) covers corrupt and truncated gzip.
  3. Scope is right: the production crash was in chat() (memory reflection). chat_stream() already degrades gracefully on garbage bodies (SSE parse-skip → no finish_reason → existing retry path), so no change needed there.
  4. +6 tests cover plain/gzip/corrupt parsing, transparent decompress, retry-then-succeed, and retry exhaustion — all pass locally (24 passed incl. doc-count guard).
  5. Doc counts synced 502 → 508 in README/README.cn/Agent.md.

CI green (Test run 31148570523). Fixes a real production crash observed at 2026-08-07 12:40 (UnicodeDecodeError on gzip body without Content-Encoding).

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle (2nd independent review)

Re-verified on commit 954349a (no changes since last review):

  • _parse_json_body(): gzip magic detection + transparent decompress is minimal and correct; plain JSON path unchanged (no behavior change for well-formed responses).
  • chat() retry path: mirrors the existing transient-error backoff policy; last_error is set before continue so exhaustion raises correctly; exception set covers corrupt (OSError/BadGzipFile) and truncated (EOFError) gzip.
  • Tests re-run locally: 22 passed (6 new: plain/gzip/corrupt parsing, transparent decompress, retry-then-succeed, retry exhaustion).
  • CI green (31148570523).

One more LGTM needed before merge.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle (3rd independent review)

Final verification on commit 954349a (unchanged since reviews 1-2):

  • Full regression suite on the PR branch: 508 passed
  • Import check + emrg --help: OK
  • CI green (31148570523)

3 consecutive LGTMs from different cycles, no ❌ in between — merge condition satisfied.

@argszero
argszero merged commit 1c7c85b into master Aug 7, 2026
1 check passed
@argszero
argszero deleted the fix/llm-gzip-body-tolerance branch August 7, 2026 04:59
argszero added a commit that referenced this pull request Aug 7, 2026
…ntry (#542)

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
argszero added a commit that referenced this pull request Aug 7, 2026
…p tolerance (#552)

Version bump 0.2.10 → 0.2.11 across all 6 version sources
(pyproject.toml / emrg/__init__.py / gui/package.json / uv.lock /
make-installer.sh / build-runtime.sh). Release for Windows verification:

- #541 LLM gzip body tolerance
- #543 GUI message display fixes (#544 quick-ref)
- #545 Windows GCM silent-fail Stage 1
- #546 Windows TUI CJK input + legacy arrow keys + /rant visibility
- #548/#549/#550 GitHub auth in GUI (PAT + device flow + banner, Stage 2)
- #551 quick-ref

All 548 tests green.

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
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