Bearer-only auth (no x-api-key) was misrouted at /v1/models and
catch-all endpoints because routing only checked x-api-key header.
Claude Code users authenticating via ANTHROPIC_AUTH_TOKEN (OAuth)
send Authorization: Bearer instead of x-api-key.
- Add is_anthropic_auth() helper detecting x-api-key, anthropic-version,
or Bearer sk-ant-* tokens
- Fix /v1/models routing to use consolidated auth detection
- Fix catch-all /{path:path} routing to recognize Bearer tokens
- Use Bearer token prefix for rate-limit key when x-api-key absent
- Add 19 tests covering auth detection, routing, and rate-limit keys
Closeschopratejas/headroom#200
Bring the branch onto upstream/main so GitHub validates the same merged
compression code locally, and install act into PATH for the
workflow-validation job.
This keeps the compact-JSON compression regression fixed while making
the shared validation script pass in CI.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix workflow validation failures by wiring detect-version outputs into all
release publish jobs, renaming the GitHub Packages skip variable to a
valid Actions variable name, and adjusting the macOS PATH export for
actionlint.
Also make min_tokens_to_compress use token counting instead of whitespace
splits so compact JSON tool outputs still compress after merging the
latest main branch changes, and add a regression test for that path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a workflow-validation CI job that installs actionlint and act,
checks the release and Docker workflows against checked-in event
fixtures, and shares the same validation script developers can run
locally.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Call the Docker workflow from the release pipeline so Docker publishes in
the same run, build npm tarballs alongside Python distributions, and
attach those artifacts to the GitHub release page.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- compress_system_messages (default True): set False to skip system
prompt compression entirely
- min_tokens_to_compress (default 250): minimum word count for a
message to be eligible for compression (was hardcoded at 50)
Both flow through CompressConfig to ContentRouter. Default behavior
unchanged.
CompressConfig.kompress_model was declared but not passed to transforms.
Now it flows through pipeline.apply() to ContentRouter, enabling:
- Custom Kompress models via HuggingFace model ID
- kompress_model="disabled" to skip ML compression entirely
(SmartCrusher + CacheAligner still run)
- Default behavior (kompress_model=None) unchanged
Derive the exact Docker image version from the release tag or manual
workflow input, sync versioned files in the build workspace before the
image build, and publish an explicit matching image tag.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses review feedback on PR #194: prior code validated X-Headroom-Stack
slugs differently at each ingress — the Prometheus counter capped length but
the env-var path and beacon didn't. Under a misconfigured or malicious client
sending arbitrary X-Headroom-Stack values, requests_by_stack could grow
unbounded in memory, in the Prometheus scrape, and in the JSONB telemetry
payload.
- New normalize_stack(raw) in headroom/telemetry/context.py: strips, lowercases,
enforces ^[a-z][a-z0-9_]{0,63}$. Single chokepoint.
- detect_stack now routes HEADROOM_STACK and stats dominant-slug through it;
invalid env values fall through to the agent-type path or default "proxy".
- PrometheusMetrics.record_stack routes through normalize_stack and rejects
new slugs once the dict hits MAX_DISTINCT_STACKS (32); existing slugs still
increment so valid callers aren't starved.
- 10 new unit tests covering normalize_stack charset/length/empty cases, the
cardinality cap, and invalid-env fallback paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two orthogonal identity fields to the anonymous telemetry beacon so we
can segment usage by integration surface and deployment shape:
- headroom_stack: how Headroom is invoked (proxy, wrap_claude, wrap_codex,
adapter_ts_openai, adapter_ts_anthropic, etc.). Resolved from HEADROOM_STACK
env, HEADROOM_AGENT_TYPE fallback, or aggregated request-header counts.
- install_mode: how the proxy is deployed (wrapped / persistent / on_demand).
Detected from HEADROOM_AGENT_TYPE plus DeploymentManifest lookup.
TS SDK adapters now tag every request with X-Headroom-Stack; a FastAPI
middleware buckets the counts and surfaces them via /stats so the beacon can
report requests_by_stack for mixed-integration sessions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Do not strip the raw git-log record before splitting on the field
separator, because commits with empty bodies lose their delimiter and get
dropped entirely. Add a deterministic unit test for empty-body parsing.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Determine the release bump from all unreleased commits since the previous
release tag and apply the highest required semantic version increment.
This keeps feat commits at a minor bump unless a breaking change requires
major, even when later patch-level commits are present.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align with SpecKit's canonical docs/ structure. Update .gitignore
comment to reflect new location.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fills the deferred TODO in docker-native-install.sh. After 'install
apply' and before 'install stop', we now:
1. 'docker inspect' the running headroom-${PROFILE} container and assert
both canonical env vars are present in Config.Env with the expected
/tmp/headroom-home/.headroom and .../config values.
2. 'docker exec env' inside the container and assert the same vars are
visible to processes running under the proxy entrypoint (proves not
just Config.Env but actual runtime visibility).
Unit tests in tests/test_install/{test_runtime,test_native_installers}
already lock install-time env forwarding; this completes the runtime
half of the guarantee.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three scenarios lock the issue-175 additive guarantee:
1. Legacy-only user (HEADROOM_SAVINGS_PATH set, canonical unset) still
gets the byte-for-byte legacy value.
2. Canonical-only user (HEADROOM_WORKSPACE_DIR set) sees every workspace
bucket resource relocate with the correct filenames.
3. Both set: legacy per-resource env var wins over canonical root env
for its own resource; other unreserved helpers still follow the new
canonical root (orthogonality).
Plus a regression guard asserting all three legacy vars (savings, toin,
subscription) simultaneously beat the canonical workspace override.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the Python coverage gap closure: every derived-only helper
(memoryDbPath, nativeMemoryDir, logDir, rtkPath, beaconLockPath,
pluginConfigDir, etc.) now has both a default test and an override test
that exercises the canonical workspace env var end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every public helper in headroom/paths.py now has both a default-derived
test and a HEADROOM_WORKSPACE_DIR/CONFIG_DIR precedence test. Adds the
derived_workspace_helpers parametrized matrix plus explicit cases for
proxy_log_path, rtk_path, beacon_lock_path, plugin_*_dir, and both
ensure_* functions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commit 997f364 landed server.py in a state that fails `ruff format
--check .`, blocking all PRs. This runs `ruff format` on the file
and nothing else.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lazy-load tomllib so Python 3.10 test collection succeeds, and apply the repo formatter change required by the 3.12 lint job.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Consolidates routine minor/patch bumps into a single weekly PR per
ecosystem (docker, github-actions, pip), cutting review overhead
roughly in half. Major updates remain ungrouped so breaking changes
still get individual review.
Closes#174
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the inline release version math with a tested helper that normalizes legacy four-part tags and computes a single semantic version for packages and GitHub releases.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>