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 <noreply@anthropic.com>
This commit is contained in:
Eyal Mizrachi 2026-06-16 22:10:01 -04:00 committed by GitHub
parent 5208e32d64
commit addebdb29c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 2 deletions

View file

@ -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

View file

@ -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