test_concurrent_compression_has_no_semaphore_tail computed
p99/max(p50,1). On a fast/quiet CI runner p50 rounds to 0ms, so the
ratio collapses to p99-in-ms and a few ms of ordinary scheduler jitter
(p50=0ms, p99=5ms) read as ~4.8x, tripping the <4x gate — noise, not the
semaphore-contention tail it targets (tens of ms, ~27x).
Only enforce the ratio once p99 clears a 25ms scheduler-noise floor
(assert ratio < 4.0 or p99 < 25ms). A real contention regression still
trips it (large absolute tail + high ratio); sub-ms jitter no longer
does. Verified locally: the test passes.
Adds TESTING-copilot-subscription.md (per-OS copy-paste test flows, what's
proven vs. needs verification, the host-native-vs-Docker discovery caveat) and a
GitHub issue template to collect structured test reports from Linux/Windows
users.
Pass the wrapper-resolved (and, for --subscription, GitHub-validated) Copilot
token to the proxy as an explicit launch argument instead of mutating the
parent process's global os.environ. The proxy pins it as
GITHUB_COPILOT_API_TOKEN, so upstream auth is deterministic rather than the
proxy re-running unvalidated token discovery (which could otherwise inject a
different token and 401). Removes the global-state mutation and the test
isolation it forced.
Add a hermetic cross-platform smoke suite (no Keychain/secret-tool/network)
proving the env-var token path resolves on any OS, each OS secret reader is
inert off-platform, and the proxy injects exactly the validated token.
get_encoding_for_model() resolved an unknown model to an encoding by
scanning MODEL_TO_ENCODING for the first key that starts with the
matched prefix. Because the gpt-4o entries are defined before the
plain gpt-4 entries, the "gpt-4" prefix matched "gpt-4o" first and
returned o200k_base for any gpt-4 snapshot not already in the table
(e.g. a future dated build like gpt-4-2025-01-01). The gpt-4 family
uses cl100k_base, so token counts for those models were computed with
the wrong encoding, skewing every downstream budget/truncation
decision.
Map each prefix directly to its encoding (still ordered most-specific
first) so the result is deterministic and independent of dict
insertion order.
Regression test in tests/test_tokenizers.py asserts unknown gpt-4 /
gpt-4-turbo snapshots resolve to cl100k_base while gpt-4o snapshots
stay on o200k_base. It fails before this change and passes after.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
_extract_json_block() counted raw [ ] { } per line via str.count() to
find where a JSON block ends. Any bracket/brace inside a JSON string
value (e.g. the "]" in {"path": "a]b"}) was counted as structural, so
the running balance hit zero early and the block was cut mid-array.
In ContentRouter._compress_mixed() this fragments one JSON array into
multiple sections: the array is truncated, a non-array fragment gets
mislabeled JSON_ARRAY, and the trailing "]" leaks into the next prose
section — so content is routed to the wrong compressor.
Walk the characters with a small in-string/escape state machine and
only count brackets/braces that are outside string literals. Behavior
is unchanged for JSON without brackets-in-strings.
Regression tests in tests/test_transforms_content_router.py cover both
the helper (_extract_json_block) and the end-to-end split
(split_into_sections). They fail before this change and pass after.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
tree-sitter >= 0.23 marks Parser as PyO3 #[pyclass(unsendable)], which
hard-panics if the object is accessed from any thread other than its
creator. The previous code cached parsers in a module-global dict and
returned them to ThreadPoolExecutor workers, triggering:
pyo3_runtime.PanicException: _native::Parser is unsendable,
but sent to another thread
Replace the shared dict + lock with threading.local() so each pool
thread creates and owns its own Parser instance. Switch from
tree_sitter_language_pack.get_parser() (returns wrapper types with
method-based API) to stock tree_sitter.Parser + get_language() to
preserve property-based Node/Tree access across all call sites.
Fixes#481.
Three changes:
1. dashboard.html: bind the 'Proxy $ Saved' hero tile and its x-show
guards to stats.persistent_savings?.lifetime?.compression_savings_usd
instead of stats.cost?.savings_usd. The cost field comes from the
in-memory CostTracker which is per-process, so round-robin /stats
polls across --workers > 1 show a different worker's partial total on
every reload. persistent_savings.lifetime is written by SavingsTracker
to proxy_savings.json and is read from disk on every /stats call, so
it converges regardless of which worker handles the poll.
2. server.py: add CostTracker to the multi-worker warning printed at
startup so operators see it alongside the existing CCR / prefix-tracker
notice.
3. savings_tracker.py: acquire an exclusive cross-process flock on the
target file before the atomic rename in _save_locked(). Without this,
concurrent workers each read the same baseline at boot, accumulate
their own deltas, and write back via tempfile+rename, clobbering each
other's increments. flock is Unix-only; Windows silently skips it.
The pattern matches the existing fcntl usage in ccr/mcp_server.py.
Route GitHub Copilot CLI subscription traffic through the Headroom
OpenAI-compatible proxy path and resolve the account-specific Copilot API
endpoint before launch.
Add source-aware Copilot token discovery for explicit Copilot env vars,
macOS Keychain, Windows Credential Manager, Linux Secret Service, credential
files, and generic GitHub fallbacks. Validate subscription candidates against
GitHub Copilot user metadata so generic GH_TOKEN/GITHUB_TOKEN values do not
shadow Copilot CLI auth.
Document the subscription command and platform status in README: macOS
Keychain auth reuse has been smoke-tested, while Windows, Linux, Docker, and
CI auth-discovery paths still need real OS validation.
Tests: .venv/bin/python -m pytest tests/test_copilot_auth.py
tests/test_copilot_macos_keychain.py tests/test_copilot_linux_secret.py
tests/test_cli/test_wrap_copilot.py tests/test_cli/test_wrap_persistent.py
tests/test_proxy_copilot_auth_hooks.py
Google deprecated gemini/gemini-2.0-flash; headroom learn silently fails
when GEMINI_API_KEY is set. PR #532 updated the default in analyzer.py
but left stale references in the CLI help text and unit test assertion.
- Remove rolling_window_config from HeadroomClient Python constructor table
(RollingWindowConfig was retired in 0.9.x)
- Fix HeadroomConfig Python example: replace config.rolling_window.preserve_recent_turns
with a note that rolling_window was removed
- Fix ccrHashes description: cross-conversation retrieval -> Compress-Cache-Retrieve
- All other doc fixes were already applied in prior commits
Headroom reads and writes its own dashboard template, JSON deployment/sync
state and provider config files using the platform default text codec. On
systems whose default encoding is not UTF-8 (e.g. Windows cp949/cp1252
locales) this raises UnicodeDecodeError when the file contains non-ASCII
bytes.
The dashboard template ships with non-ASCII UTF-8 content, so loading the
dashboard crashes on a Korean Windows locale at byte 20523 (fixes#533).
The same latent bug exists in the sibling JSON state/config I/O; since these
files are owned by Headroom and JSON is UTF-8 by spec (RFC 8259 §8.1), read
and write them with an explicit encoding="utf-8" so they round-trip on every
platform.
Add a regression test covering the dashboard load and a non-ASCII JSON
state round-trip.
The package-lock.json was already bumped to 16.2.6 in a prior commit,
but bun.lock still pinned next at 16.2.4 (vulnerable to Image Optimization
API DoS per GHSA-h64f-5h5j-jqjh). This ensures all lockfiles are consistent.
VIPER hash: 835f8d5b1d2d350d
Refs: GHSA-h64f-5h5j-jqjh / CVE-2026-44577