From addebdb29c3b4a877ed46553d9b0c0a128d62cef Mon Sep 17 00:00:00 2001 From: Eyal Mizrachi <145741922+Eyalm321@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:10:01 -0400 Subject: [PATCH] feat(proxy): make COMPRESSION_TIMEOUT_SECONDS configurable via env (#946) (#991) ## Description The compression-pipeline timeout was hard-coded at 30s, so slow CPUs and long Claude Code conversations had no recourse. #946 asks to wire `HEADROOM_COMPRESSION_TIMEOUT_SECONDS` through. Refs #946. ## Type of Change - [x] New feature (non-breaking change that adds functionality) ## Changes Made - Read `HEADROOM_COMPRESSION_TIMEOUT_SECONDS` from the environment (float), falling back to 30 on an unparseable value. ## Testing - [x] Unit tests pass (`pytest`) - [x] New tests added for new functionality ### Test Output ```text $ pytest tests/test_proxy/test_compression_timeout_config.py -q 4 passed in 0.09s ``` ## Real Behavior Proof - Environment: Linux, Python 3.13, editable build of this branch - Exact command / steps: `HEADROOM_COMPRESSION_TIMEOUT_SECONDS=88 python -c "import headroom.proxy.helpers as h; print(h.COMPRESSION_TIMEOUT_SECONDS)"` - Observed result: prints `88.0` (default `30.0`; an unparseable value falls back to `30.0`) - Not tested: a live compression actually exceeding the configured timeout under real load ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review --------- Co-authored-by: Claude Opus 4.8 --- headroom/proxy/helpers.py | 11 ++++- .../test_compression_timeout_config.py | 41 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/test_proxy/test_compression_timeout_config.py diff --git a/headroom/proxy/helpers.py b/headroom/proxy/helpers.py index a3bbc91f6..2c8c116b4 100644 --- a/headroom/proxy/helpers.py +++ b/headroom/proxy/helpers.py @@ -770,8 +770,15 @@ def parse_sse_events_from_byte_buffer( # Maximum message array length (prevents DoS from deeply nested payloads) MAX_MESSAGE_ARRAY_LENGTH = 10000 -# Compression pipeline timeout in seconds -COMPRESSION_TIMEOUT_SECONDS = 30 +# Compression pipeline timeout in seconds. Override via the +# HEADROOM_COMPRESSION_TIMEOUT_SECONDS env var for slow CPUs or long Claude Code +# conversations (GH #946). Falls back to 30 on an unparseable value. +try: + COMPRESSION_TIMEOUT_SECONDS = float( + os.environ.get("HEADROOM_COMPRESSION_TIMEOUT_SECONDS", "30") + ) +except ValueError: + COMPRESSION_TIMEOUT_SECONDS = 30.0 # Maximum compression cache sessions (prevents unbounded memory growth) MAX_COMPRESSION_CACHE_SESSIONS = 500 diff --git a/tests/test_proxy/test_compression_timeout_config.py b/tests/test_proxy/test_compression_timeout_config.py new file mode 100644 index 000000000..3ee420a56 --- /dev/null +++ b/tests/test_proxy/test_compression_timeout_config.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import importlib +import os + +import pytest + +import headroom.proxy.helpers as helpers + + +@pytest.fixture(autouse=True) +def _restore_default_timeout(): + """Each test reloads helpers under a chosen env; reset to the default afterwards + so the reloaded module constant doesn't leak into the rest of the suite.""" + yield + os.environ.pop("HEADROOM_COMPRESSION_TIMEOUT_SECONDS", None) + importlib.reload(helpers) + + +def test_default_timeout_is_30(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("HEADROOM_COMPRESSION_TIMEOUT_SECONDS", raising=False) + importlib.reload(helpers) + assert helpers.COMPRESSION_TIMEOUT_SECONDS == 30.0 + + +def test_env_overrides_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("HEADROOM_COMPRESSION_TIMEOUT_SECONDS", "75") + importlib.reload(helpers) + assert helpers.COMPRESSION_TIMEOUT_SECONDS == 75.0 + + +def test_fractional_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("HEADROOM_COMPRESSION_TIMEOUT_SECONDS", "12.5") + importlib.reload(helpers) + assert helpers.COMPRESSION_TIMEOUT_SECONDS == 12.5 + + +def test_bad_env_falls_back_to_default(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("HEADROOM_COMPRESSION_TIMEOUT_SECONDS", "not-a-number") + importlib.reload(helpers) + assert helpers.COMPRESSION_TIMEOUT_SECONDS == 30.0