Skip to content

chore: sync canonical upstream - #1

Merged
br4vesirrobin merged 18 commits into
mainfrom
chore/sync-upstream-readme-autobahn
Jul 14, 2026
Merged

chore: sync canonical upstream#1
br4vesirrobin merged 18 commits into
mainfrom
chore/sync-upstream-readme-autobahn

Conversation

@br4vesirrobin

Copy link
Copy Markdown

Summary

  • merge the current headroomlabs-ai/headroom main into the maintained fork
  • restore a safe fork relationship for umbrella checkout reconciliation

Validation

  • merge completed without conflicts
  • hosted CI is the fail-closed validation gate

Autobahn dependency

This unblocks the AiTool workspace fresh-clone ./setup.sh smoke test; merge only when every hosted check is green.

chopratejas and others added 11 commits July 13, 2026 16:33
…law (headroomlabs-ai#1969) (headroomlabs-ai#2120)

## Description
`headroom wrap openclaw` installed a non-existent npm spec — the
`--plugin-spec` default was `headroom-ai/openclaw`, which npm reads as a
GitHub shorthand and fails; the published package is `headroom-openclaw`
(see `plugins/openclaw/package.json`).

Fix introduces a single `OPENCLAW_NPM_PACKAGE = "headroom-openclaw"`
constant (kept in sync with `package.json` and the release env), uses it
as the default, and defers writing the `plugins.entries.headroom` config
until after a successful install so a hard failure leaves no stale
entry.

Closes headroomlabs-ai#1969

## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made
- `headroom/providers/openclaw/wrap.py` + `__init__.py`: canonical
`OPENCLAW_NPM_PACKAGE` constant.
- `headroom/cli/wrap.py`: use it as `--plugin-spec` default; write
config only after successful install.
- `tests/test_cli/test_wrap_openclaw.py`: expect `headroom-openclaw`;
install-before-config ordering; failed-install-writes-no-config test.

## Testing
- [x] Unit tests pass (`pytest tests/test_cli/test_wrap_openclaw.py`) —
29 passed
- [x] Linting passes (`ruff check`)
### Test Output
```text
29 passed
ruff: All checks passed!
```

## Real Behavior Proof
- Before: `wrap openclaw` → npm "unsupported spec" error; a failed
install left a stale config entry.
- After: installs `headroom-openclaw`; no config written on failure.
…ched prompts (headroomlabs-ai#2110) (headroomlabs-ai#2119)

## Description
`DynamicContentDetector` / `RegexDetector` in
`headroom/cache/dynamic_detector.py` (used by the `cache_aligner`
transform) misclassified ordinary English words and code identifiers
(e.g. `in_pr`) as "dynamic content," extracting them from the system
prompt and re-appending a `[Dynamic Context]` tail that grows
unboundedly and corrupts the cached prompt over a session.

Fix tightens detection to require genuinely-dynamic shapes (timestamps,
UUIDs, hashes, numbers-with-units, ISO dates) rather than bare tokens —
no hardcoded wordlist — and bounds the tail. `cache_aligner` is off by
default, so blast radius is limited, but the detector logic is now
correct.

Closes headroomlabs-ai#2110

## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made
- `headroom/cache/dynamic_detector.py`: raise the evidence bar so
ordinary words/identifiers aren't extracted; bound the dynamic tail.
- `tests/test_cache/test_dynamic_detector.py`: assert false positives
(ordinary words/identifiers) are NOT extracted while real dynamic values
still are.

## Testing
- [x] Unit tests pass (`pytest
tests/test_cache/test_dynamic_detector.py`)
- [x] Linting passes (`ruff check`)
### Test Output
```text
55 passed, 2 skipped
ruff: All checks passed!
```

## Real Behavior Proof
- Before: identifiers like `in_pr` extracted into a growing `[Dynamic
Context]` tail, corrupting cached prompts.
- After: ordinary tokens stay in place; only genuinely-dynamic values
are detected.
…nt) (headroomlabs-ai#2153)

## What
One-line type fix: `_EMBEDDER_CACHE` is keyed by a 3-tuple `(backend,
model, ollama_base_url)` but was still annotated `dict[tuple[str, str],
Embedder]`.

## Why
`mypy headroom --ignore-missing-imports` (the CI `lint` job) fails on
`main` at `factory.py:187`/`:219` because of this mismatch. Since the
lint job runs whole-package mypy, **every open PR is currently failing
lint on this bug** — none of them introduced it. This unblocks the lint
gate repo-wide.

## Proof
`mypy headroom --ignore-missing-imports` → `Success: no issues found in
469 source files`. `ruff check .` + `ruff format --check .` clean.

## Scope
Type annotation only; no runtime behavior change. The 3-tuple key itself
is pre-existing (the Ollama base_url was already part of the key to
avoid cross-server embedder cache collisions).
…2097)

## Description

Pip-audit found 6 vulnerabilities in 2 packages in the lockfile:

| Package | From | To | Vulns Fixed |
|---------|------|----|-------------|
| click | 8.3.1 | 8.4.2 | PYSEC-2026-2132 |
| pillow | 12.2.0 | 12.3.0 | PYSEC-2026-2253~2257 |

Closes #N/A (no issue filed — security workflow failure)

## Type of Change

- [x] Bug fix (non-breaking)
- [ ] New feature (non-breaking)
- [ ] Breaking change
- [ ] Documentation update

## Changes Made

- `uv.lock`: Upgraded click from 8.3.1 to 8.4.2, pillow from 12.2.0 to
12.3.0

## Testing

- [x] `uv lock --upgrade-package` resolved cleanly
- [x] `ruff check headroom/` passes
- [x] CLI import verified (click 8.4.2 loads correctly)

```
$ uv run python -c "import click; print(click.__version__)"
click: 8.4.2

$ uv tree --depth=1 | grep -E "click|pillow"
click v8.4.2
pillow v12.3.0 (extra: all)
pillow v12.3.0 (extra: image)
```

## Real Behavior Proof

- Environment: headroom main (upstream/main c365c7f), uv-managed Python
3.12
- Exact command / steps: `uv lock --upgrade-package "pillow>=12.3.0"
--upgrade-package "click>=8.3.3"` on upstream/main; then `git diff
--stat`, `uv run python -c "import click; print(click.__version__)"`
- Observed result: uv.lock only changed; click 8.3.1 → 8.4.2, pillow
12.2.0 → 12.3.0; ruff passes; no test regressions
- Not tested: E2E proxy (dependency-only change — no code changes)

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

Co-authored-by: lennney <lennney@users.noreply.github.com>
…labs-ai#1815)

## Description

Compress Hermes Studio scoped coding-agent passthrough requests in the
generic OpenAI passthrough handler. Hermes can route scoped Claude Code
and Codex traffic through Headroom while preserving its own proxy paths;
this PR keeps Hermes responsible for scoped proxy
authentication/provider adaptation while still applying Headroom
compression to supported chat payloads before forwarding.

The compression remains narrow-scoped:
- Only chat messages with `user` or `assistant` roles are compressed.
- Tool, function, reasoning, and system items are preserved byte-stable.
- Non-dict items in the Responses `input` array are preserved and
spliced back.

## Type of Change

- [ ] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Detect `/api/codex-proxy/.../v1/responses` paths and compress
supported Responses `input` chat items before forwarding.
- Detect `/api/claude-code-proxy/.../v1/messages` paths and compress
supported Anthropic `messages` payloads before forwarding.
- Preserve bypass, malformed payload, missing-model, tool/function,
reasoning/system, and non-dict passthrough behavior.
- Add regression coverage in
`tests/test_hermes_passthrough_compression.py`.

## Testing

- [x] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
$ python -m pytest tests/test_hermes_passthrough_compression.py -v
test_codex_proxy_preserves_tool_and_function_items PASSED
test_codex_proxy_preserves_nondict_items PASSED
test_codex_proxy_bypass_header_skips_compression PASSED
test_codex_proxy_malformed_input_preserved PASSED
test_codex_proxy_compression_applies_to_chat_messages PASSED
test_claude_proxy_preserves_tool_use_items PASSED
test_claude_proxy_bypass_header_skips_compression PASSED
test_claude_proxy_no_model_forwarded_unchanged PASSED
test_claude_proxy_compression_applies_to_chat_messages PASSED
test_non_hermes_routes_not_affected PASSED
```

## Real Behavior Proof

- Environment: Author-reported local test environment for
`headroom/proxy/handlers/openai.py` and
`tests/test_hermes_passthrough_compression.py`.
- Exact command / steps: `python -m pytest
tests/test_hermes_passthrough_compression.py -v`.
- Observed result: The 10 Hermes passthrough regression tests passed,
covering Codex and Claude scoped proxy routes plus preservation/bypass
cases.
- Not tested: End-to-end Hermes Studio traffic against a live upstream
service is not covered by this PR body evidence.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A.

## Additional Notes

Generated with Claude Code. The unchecked checklist items are not
required for this narrow proxy-handler test change.

---------

Co-authored-by: x1051445024 <你的GitHub注册邮箱>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
…eadroomlabs-ai#2037) (headroomlabs-ai#2127)

## Description

`_resolve_openai_upstream_base` ran the `X-Headroom-Base-Url` value
through `_normalize_origin`, which strips the path. A custom
OpenAI-compatible upstream served from a sub-path, such as
`https://host/api/v1`, was routed to the bare origin and returned
`proxy_error` (headroomlabs-ai#2037). This re-attaches the path after origin
normalization.

This is a clean extraction of the path fix from headroomlabs-ai#2047, which bundled it
with an unrelated `supports_websockets = true` to `false` default change
across init/wrap/codex. headroomlabs-ai#2047 can be closed in favor of this narrower
fix.

Closes headroomlabs-ai#2037

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `headroom/proxy/handlers/openai.py`: re-attach the request header path
component in `_resolve_openai_upstream_base` after origin normalization.
- `tests/test_proxy/test_openai_upstream_header.py`: assert sub-paths
are preserved and trailing slashes are normalized.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ pytest tests/test_proxy/test_openai_upstream_header.py
5 passed

$ ruff check headroom/proxy/handlers/openai.py tests/test_proxy/test_openai_upstream_header.py
All checks passed!
```

## Real Behavior Proof

- Environment: local proxy header resolution path, custom
OpenAI-compatible upstream configured via `X-Headroom-Base-Url`.
- Exact command / steps: resolve `X-Headroom-Base-Url:
https://gateway.example/api/v1` through `_resolve_openai_upstream_base`
/ `_resolve_openai_upstream`.
- Observed result: before the fix, the upstream resolved to
`https://gateway.example` and lost `/api/v1`, causing the proxy to route
to the wrong endpoint. After the fix, it resolves to
`https://gateway.example/api/v1`; a trailing slash is normalized away.
- Not tested: end-to-end request against a live third-party
OpenAI-compatible gateway. The regression is covered at the proxy
routing helper layer where the path was dropped.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A.

## Additional Notes

- Documentation is not updated because this fixes the existing header
behavior rather than changing a documented user-facing contract.
- Changelog is not updated in this PR; the change is scoped to the
regression and test.
- `mypy headroom` was not run in the author's workflow.
…rse (headroomlabs-ai#2129)

## Description

The Bedrock Converse API hard-rejects any request containing a tool name
over 64 characters (`toolConfig.tools.N.member.toolSpec.name`). Claude
Code includes every globally-added claude.ai MCP connector tool in every
request it sends, even connectors the user hasn't enabled locally. One
org-wide connector with a 65-char tool name is enough to fail every
single request routed through this backend's Bedrock path, with no way
to remove or disable the connector client-side.

Direct Bedrock mode (`CLAUDE_CODE_USE_BEDROCK=1`, bypassing this proxy)
is unaffected: it hits Bedrock's native Anthropic-compatible endpoint,
which has no such length limit. Only the Converse API, which this
LiteLLM-backed `bedrock` provider path uses, enforces it.


## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `headroom/backends/litellm.py`: `send_message` and `stream_message`
both filter tools with names over 64 characters out of the payload
before converting/forwarding, but only for `self.provider == "bedrock"`.
Other providers are untouched.
- `tests/test_backend_bugs.py`: new
`TestBedrockOversizedToolNameFiltering` covering both `send_message` and
`stream_message` — an oversized (65-char) name is dropped on `bedrock`,
a name at exactly the 64-char boundary is kept, and non-`bedrock`
providers forward oversized names unfiltered (the limit is a Bedrock
Converse constraint, not a general one).
- `CHANGELOG.md`: added a `### Fixed` entry under `Unreleased`.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ uv run pytest tests/test_backend_bugs.py tests/test_backend_anyllm.py -q
============================= test session starts ==============================
platform darwin -- Python 3.13.13, pytest-9.0.3, pluggy-1.6.0
collected 57 items

tests/test_backend_bugs.py ..........................................    [ 73%]
tests/test_backend_anyllm.py ...............                             [100%]

============================== 57 passed in 1.42s ==============================

$ uv run ruff check headroom/backends/litellm.py tests/test_backend_bugs.py
All checks passed!

$ uv run mypy headroom/backends/litellm.py
Success: no issues found in 1 source file
```

## Real Behavior Proof

- **Environment:** personal fork deployed as a real proxy (macOS launchd
service, `headroom install apply`) with `--backend bedrock --mode token
--code-aware --bedrock-profile sso-bedrock`, fronting a live Claude Code
session with a globally-added-but-not-locally-enabled claude.ai MCP
connector (`TopCounsel`) whose tool name is 65 characters.
- **Exact command / steps:** run any Claude Code request through this
deployment while the org-wide `TopCounsel` connector is present (it is
included in the tool list on every request regardless of local
enablement).
- **Observed result:** before the fix, every request failed with a
LiteLLM `BedrockException`: `1 validation error detected: Value
'mcp__claude_ai_TopCounsel_by_The_L_Suite__complete_authentication' at
'toolConfig.tools.N.member.toolSpec.name' failed to satisfy constraint:
Member must have length less than or equal to 64`. After applying the
fix (filtering the oversized tool out before the LiteLLM call), the same
session proceeds normally with no validation error, confirmed live
against this deployment.
- **Not tested:** truncating the name instead of dropping it was tried
and discarded during investigation — the model echoes the truncated name
back in `tool_use` blocks, and Claude Code matches tool calls by the
original full name, so truncation breaks routing on the return path.
This PR drops the tool entirely rather than truncating, which is why it
is not present as an alternative in the diff.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A — backend logic change, no UI surface.

## Additional Notes

- "I have made corresponding changes to the documentation" is unchecked:
no file in `docs/`, `README.md`, or `CONTRIBUTING.md` documents
backend-specific tool-list filtering behavior, so there is no existing
section to update.
- No linked issue number: this was found via independent investigation
of a personal deployment (a live Bedrock validation failure), not filed
as a `headroomlabs-ai/headroom` issue first. Checked `gh pr list`/`gh
issue list` for existing coverage of "Bedrock Converse 64-char tool
name" and found none open or merged.
- A native Bedrock Anthropic-compatible endpoint backend (avoiding
Converse's tool-name limit entirely) would be the more complete
long-term fix, but is out of scope for this PR.

---------

Co-authored-by: Ingmar Krusch <ingmar.krusch@gmail.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
…i#2132)

Fixes headroomlabs-ai#2131.

## Description

`plugin_config_dir` / `plugin_workspace_dir` rejected `/` and `\` in the
plugin name but accepted `.` and `..`. Since the returned path is
`<root> / "plugins" / name`, `plugin_config_dir("..")` resolved to the
whole config root and `plugin_workspace_dir("..")` to the whole
workspace root: savings ledger, memory DB, license cache, logs, and
every other plugin's state. That defeated the sandbox the helper was
written to enforce.

Both callers are folded onto a shared `_validate_plugin_name` that
rejects the empty string, both path separators, `.`, `..`, and NUL.

Closes headroomlabs-ai#2131

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `headroom/paths.py`: added `_validate_plugin_name` and shared it
across `plugin_config_dir` and `plugin_workspace_dir`.
- `tests/test_paths.py`: expanded invalid-name coverage for `.`, `..`,
and NUL and added a sandbox-escape regression test.
- `CHANGELOG.md`: noted the path traversal fix.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ uv run pytest tests/test_paths.py -q
...........................................................................  [100%]
79 passed in 0.14s

$ uv run ruff check headroom/paths.py tests/test_paths.py
All checks passed!

$ uv run ruff format --check headroom/paths.py tests/test_paths.py
2 files already formatted
```

## Real Behavior Proof

- Environment: macOS 25.4 (Darwin arm64), Python 3.12.13, `uv 0.11.28`,
branch `fix/plugin-path-traversal`.
- Exact command / steps: set `HEADROOM_CONFIG_DIR=/tmp/hc` and
`HEADROOM_WORKSPACE_DIR=/tmp/hw`, then call `plugin_config_dir("..")`,
`plugin_config_dir(".")`, and `plugin_config_dir("legit-plugin")`.
- Observed result: before the patch, `plugin_config_dir("..")` resolved
to `/private/tmp/hc`, escaping the plugin sandbox. After the patch,
`plugin_config_dir("..")` and `plugin_config_dir(".")` raise
`ValueError`; a normal plugin name resolves under
`/private/tmp/hc/plugins/legit-plugin`.
- Not tested: Windows behavior and plugin-registry integration. The
added check is a pure string validation at the path-helper layer.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A.

## Additional Notes

- Documentation is not updated because this is a helper-level sandbox
fix rather than a user-facing behavior change; the changelog entry
captures it.
- `mypy headroom` was not run in the author's workflow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
…eadroomlabs-ai#2134)

## Description

`compress(messages, config=my_cfg, protect_recent=0, target_ratio=0.2)`
used to write those kwarg values onto the caller's `my_cfg` object — so
a shared per-agent `CompressConfig` was silently rewritten every time a
call passed a single override. The next call that did NOT override that
field then saw the previous request's value instead of the original
default.

Copy the config once at entry with `dataclasses.replace` before applying
kwarg overrides (and before the savings-profile pass, which also mutates
in place). Existing behavior for callers that pass **only** kwargs, or
**only** a config, is unchanged.

Issue headroomlabs-ai#2133 has the root-cause walkthrough.

Closes headroomlabs-ai#2133

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `headroom/compress.py`: copy the incoming `CompressConfig` once at
entry with `dataclasses.replace` before applying kwarg overrides, so the
caller's object is no longer mutated. The savings-profile branch already
did a defensive `replace(cfg)`; that copy is now hoisted up front so
both the kwarg and profile paths share the same guarantee.
- `tests/test_compress_api.py`: added
`test_kwargs_do_not_mutate_caller_config`, which fails on unpatched
`main` and passes on this branch, covering the previously broken kwarg
leg.
- `CHANGELOG.md`: noted the fix.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ uv run pytest tests/test_compress_api.py -q
.................                                                        [100%]
17 passed in 2.88s

$ uv run ruff check headroom/compress.py tests/test_compress_api.py
All checks passed!

$ uv run ruff format --check headroom/compress.py tests/test_compress_api.py
2 files already formatted
```

## Real Behavior Proof

- Environment: macOS 25.4 (Darwin arm64), Python 3.12.13, `uv 0.11.28`,
branch `fix/compress-mutates-caller-config`, model
`claude-sonnet-4-5-20250929` used for token counting.
- Exact command / steps: build `c = CompressConfig(protect_recent=4,
target_ratio=0.8)`, call `compress(msgs,
model="claude-sonnet-4-5-20250929", config=c, protect_recent=0,
target_ratio=0.2)` on a 3000-char user message, then read
`c.protect_recent` and `c.target_ratio` back (full snippet run via `uv
run python <<'PY' ... PY` — see the code block below).
- Observed result: before the patch, `c.protect_recent` became `0` and
`c.target_ratio` became `0.2` (caller's config silently rewritten).
After the patch, `c.protect_recent` stays `4` and `c.target_ratio` stays
`0.8`; caller's config unchanged. `uv run pytest
tests/test_compress_api.py` reports 17 passed including the new
`test_kwargs_do_not_mutate_caller_config` case.
- Not tested: end-to-end proxy path with `savings_profile` set (the
pre-fix code already did a defensive `replace(cfg)` on that branch, so
the profile leg was safe; this change hoists that copy up front and the
added unit test covers the kwarg leg that was broken — I did not spin up
the proxy to reconfirm the profile branch end-to-end). No
concurrent-caller / threading regression test was added — the fix
removes the mutation entirely which sidesteps the race, but there is no
explicit multi-thread reproducer.

### Reproducer

**Before the patch (unpatched `main`)**

```text
before: protect_recent=4, target_ratio=0.8
after : protect_recent=0, target_ratio=0.2       # <-- caller's cfg silently rewritten
caller's config MUTATED
```

**After the patch (this branch)**

```text
$ uv run python <<'PY'
from headroom.compress import compress, CompressConfig
c = CompressConfig(protect_recent=4, target_ratio=0.8)
print(f"before: protect_recent={c.protect_recent}, target_ratio={c.target_ratio}")
msgs = [{"role":"user","content":"x"*3000}]
compress(msgs, model="claude-sonnet-4-5-20250929", config=c, protect_recent=0, target_ratio=0.2)
print(f"after : protect_recent={c.protect_recent}, target_ratio={c.target_ratio}")
print("caller's config", "unchanged" if (c.protect_recent, c.target_ratio) == (4, 0.8) else "MUTATED")
PY
before: protect_recent=4, target_ratio=0.8
after : protect_recent=4, target_ratio=0.8
caller's config unchanged
```

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation <!-- N/A:
no user-facing doc covers the CompressConfig / kwargs contract; see
Additional Notes -->

- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- **Documentation checklist item** — left unchecked as N/A. The behavior
being fixed is internal to `headroom.compress.compress()`; the mutation
contract of `CompressConfig` + kwargs is not covered in any user-facing
doc (`wiki/compression.md`, `wiki/text-compression.md`,
`wiki/image-compression.md`, and `docs/content/docs/shared-context.mdx`
document a different / higher-level API surface). The `CHANGELOG.md`
entry is the appropriate place for this fix.
- **`mypy headroom` checklist item** — left unchecked because I did not
run it in this workflow; the change is a two-line refactor within a
well-typed function and no signatures moved.
- The prior body's `## Summary`, `## Test plan`, and `## Real behavior
proof` sections were reorganized into the six template-required headings
so the PR-governance check passes. All technical content (root-cause,
before/after reproducer, and test output) is preserved above; no code
changes were made in this update.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
…apacity (headroomlabs-ai#2136)

Fixes headroomlabs-ai#2135.

## Summary

`SharedContext.put` ran `_evict_if_needed` before writing, and the
eviction loop only checked `len(self._entries) >= self._max_entries`.
When a caller updated a key that was already cached at capacity, the put
would not have grown the map — but the loop still dropped the oldest
unrelated entry.

Same defect class as fixed for `SemanticCache` in headroomlabs-ai#2094: the eviction
path must know the incoming key so an update is not treated as an
insert. This mirrors that fix over to `SharedContext`.

Threads the incoming key through `_evict_if_needed` and skips capacity
eviction when it names an entry that already exists. Expired-entry
cleanup still runs unconditionally.

Issue headroomlabs-ai#2135 has the reproduction and impact writeup.

## Test plan

- [x] `uv run pytest tests/test_shared_context.py` — 16 passed (added
`test_updating_existing_key_at_capacity_does_not_evict`).
- [x] `uv run ruff check headroom/shared_context.py
tests/test_shared_context.py` — clean.
- [x] `uv run ruff format --check headroom/shared_context.py
tests/test_shared_context.py` — already formatted.

## Real behavior proof

**Setup:** macOS 25.4 (Darwin arm64), Python 3.12.13, `uv 0.11.28`, this
branch (`fix/shared-context-evict-on-update`).

**Before the patch (unpatched `main`)**

\`\`\`
before update: ['a', 'b', 'c']
after update: ['b', 'c'] # <-- 'a' evicted, even though 'c' was an
update
\`\`\`

**After the patch (this branch)**

\`\`\`
\$ uv run python <<'PY'
from headroom.shared_context import SharedContext
ctx = SharedContext(ttl=3600, max_entries=3)
ctx.put(\"a\", \"x\"*400)
ctx.put(\"b\", \"x\"*400)
ctx.put(\"c\", \"x\"*400)
print(\"before update:\", sorted(ctx.keys()))
ctx.put(\"c\", \"y\"*400)   # update existing at capacity
print(\"after update: \", sorted(ctx.keys()))
print(\"c value:\", ctx.get(\"c\", full=True)[:12] + \"...\")
PY
before update: ['a', 'b', 'c']
after update:  ['a', 'b', 'c']
c value: yyyyyyyyyyyy...
\`\`\`

**Test output**

\`\`\`
\$ uv run pytest tests/test_shared_context.py -q
................ [100%]
16 passed in 2.17s
\`\`\`

**What I did NOT test**

- Multi-thread test — the fix is inside the existing `self._lock`, so
serialization semantics are unchanged; I did not add a concurrent-put
stress test.
- Interaction with TTL expiry AND capacity in one call — the existing
`test_evicts_oldest_at_capacity` and `test_expired_entry_returns_none`
still pass, but I did not add a combined case.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
…oomlabs-ai#2027)

## Description

Scopes the `[1m]` context-window tier suffix sanitizer to Anthropic
`/v1/messages` requests only (addresses PR headroomlabs-ai#2027 review feedback). The
original patch applied the rewrite to every buffered compressible
endpoint, which would have silently mutated OpenAI Chat Completions and
OpenAI Responses request model IDs. The `[1m]` marker is an
Anthropic/Claude Code compatibility signal emitted by the Headroom CLI;
the existing Python parity behavior (`sanitize_anthropic_model_id()`) is
Anthropic-specific and must not leak onto OpenAI shapes.

Refactors the helper into
`compression::sanitize_anthropic_model_id_in_body`, drops the dead
`sanitize_model_id` helper in `sse/anthropic.rs`, and adds 8 unit tests
+ 5 wiremock-backed integration tests that pin the scope. All 420
`headroom-proxy` tests pass; `cargo fmt` and `cargo clippy -D warnings`
clean.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Move `sanitize_request_model_id` out of `proxy.rs` and into
`compression::sanitize_anthropic_model_id_in_body` (Anthropic-specific
name; private `trim_anthropic_model_id_suffix` helper for unit-testable
pure behavior).
- Gate the call site on `CompressibleEndpoint::AnthropicMessages`
**after** classification. The OpenAI Chat Completions and OpenAI
Responses arms get an explicit no-op match so the sanitizer cannot
re-apply to those paths.
- Drop the dead `sanitize_model_id` helper in `sse/anthropic.rs` (it was
`#[allow(dead_code)]` with no callers).
- 8 new unit tests in `compression/mod.rs`: trailing `[1m]` stripped,
Claude-style suffix stripped, no-suffix passthrough (byte-equal),
non-string model, missing `model` field, non-JSON body, `[1m]`
mid-string, and the pure trim helper.
- 5 new integration tests in
`tests/integration_anthropic_model_sanitize.rs` that boot a real Rust
proxy in front of a wiremock upstream.

## Testing

- [x] Unit tests pass (`cargo test -p headroom-proxy` → 420 passed, 35
suites)
- [x] Linting passes (`cargo clippy -p headroom-proxy --tests
--all-features -- -D warnings` clean)
- [x] Type checking passes (`cargo check -p headroom-proxy --tests
--all-features` clean)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ cargo test -p headroom-proxy --test integration_anthropic_model_sanitize
   Compiling headroom-proxy v0.x.x
    Finished `test` profile [unoptimized + debuginfo] target(s)
    Running tests/integration_anthropic_model_sanitize.rs

test anthropic_messages_strips_1m_suffix_glm ... ok
test anthropic_messages_strips_1m_suffix_claude ... ok
test anthropic_messages_passthrough_when_no_suffix ... ok
test openai_chat_completions_passthrough_with_1m_model ... ok
test openai_responses_passthrough_with_1m_model ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

```text
$ cargo test -p headroom-proxy
test result: ok. 420 passed; 0 failed; 0 ignored; 0 measured; 235 filtered out
finished in 10.93s
```

```text
$ cargo clippy -p headroom-proxy --tests --all-features -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s)
```

## Real Behavior Proof

- **Environment:** macOS 14.x; `rustc` pinned via `rust-toolchain.toml`;
`cargo` 1.x. No network access required (wiremock upstream).
- **Exact command / steps:**
1. `cargo test -p headroom-proxy --test
integration_anthropic_model_sanitize` — confirms `/v1/messages` strips
`glm-5.2[1m]` and `claude-3-7-sonnet[1m]`; confirms
`/v1/chat/completions` and `/v1/responses` leave the body byte-equal
(SHA-256 asserted).
  2. `cargo test -p headroom-proxy` — full suite green (420 passed).
3. `cargo clippy -p headroom-proxy --tests --all-features -- -D
warnings` — clean.
  4. `cargo fmt -p headroom-proxy --check` — clean.
5. Source inspection of `crates/headroom-proxy/src/proxy.rs` after the
change: the call site is now in a `match endpoint` arm that explicitly
returns `buffered` for the OpenAI variants, so the sanitizer cannot
re-apply to those paths.
- **Observed result:** all 5 new integration tests pass, all 420 crate
tests pass, clippy and fmt clean. The OpenAI tests assert SHA-256 byte
equality on a body whose `model` field ends in `[1m]`; if the sanitizer
were to re-leak onto OpenAI shapes these would fail loudly with a length
delta.
- **Not tested:** a live Anthropic API call (would require real
credentials and is not required to prove the byte-level scope fix). The
Python proxy's `sanitize_anthropic_model_id()` is the documented parity
reference (Python PR headroomlabs-ai#1840, issue headroomlabs-ai#1812).

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation (N/A — no
user-facing docs change; the Python proxy's
`sanitize_anthropic_model_id` is the parity reference cited in code
comments)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable (project uses git
log + PR titles; this PR's title follows the conventional commit shape)

## Screenshots (if applicable)

N/A — backend behavior, no UI change.

## Additional Notes

- The OpenAI integration tests rely on a JWT-style `Authorization:
Bearer` header to classify the request as `AuthMode::OAuth` and
short-circuit the PR-E4 `prompt_cache_key` injector. This is the same
control variable the existing `integration_chat_completions.rs` tests
use to isolate dispatcher byte-fidelity from the E4 hook. Comments in
each test explain the relationship.
- The dead helper in `sse/anthropic.rs` is removed, so the diff is net
negative on LoC for the SSE module.
- The Python parity reference is `sanitize_anthropic_model_id()` (Python
PR headroomlabs-ai#1840, issue headroomlabs-ai#1812); the function name and the call-site scope are
the explicit parity contract.
- Branch was rebased onto `upstream/main` (91 commits behind) before
force-push to the fork; conflict-free rebase. The original PR commit and
the fix are the only two commits on the PR.

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Abhishek Mittal <abhishek.mittal@users.noreply.github.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
@github-actions

Copy link
Copy Markdown

PR governance

This PR does not yet satisfy the required template fields:

  • Missing required section Description.
  • Missing required section Type of Change.
  • Missing required section Changes Made.
  • Missing required section Testing.
  • Missing required section Real Behavior Proof.
  • Missing required section Review Readiness.
  • Check I have performed a self-review before requesting human review.
  • Check This PR is ready for human review or convert the PR back to draft.

Please update the PR body, or move the PR back to draft while it is still in progress.

@github-actions github-actions Bot added the status: needs author action Pull request body or readiness checklist still needs author updates label Jul 14, 2026
@br4vesirrobin
br4vesirrobin force-pushed the chore/sync-upstream-readme-autobahn branch from 170400f to 258441d Compare July 14, 2026 01:13
@br4vesirrobin
br4vesirrobin merged commit 6a51758 into main Jul 14, 2026
64 checks passed
@br4vesirrobin
br4vesirrobin deleted the chore/sync-upstream-readme-autobahn branch July 14, 2026 02:46
br4vesirrobin pushed a commit that referenced this pull request Jul 29, 2026
…eadroomlabs-ai#2123)

## Description

`verbosity._ordered_events` and `_parse_session` disagree about empty
assistant turns, which desyncs the response list and produces spurious
fast-skips.

`_parse_session` only creates a `_Response` when an assistant message
actually said something:

```python
if words > 0 or out_tok > 0:
    responses.append(_Response(...))
```

But `_ordered_events` consumes one `responses[ri]` for **every**
assistant line, with no matching filter:

```python
if ltype == "assistant" and ri < len(responses):
    out.append((responses[ri].ts, "assistant", responses[ri]))
    ri += 1
```

So an assistant turn with no text and no output tokens — for example a
pure `tool_use` turn where `usage` is absent — creates no `_Response` at
parse time, yet still consumes a slot in `_ordered_events`. That slot
actually belongs to a *later* real response, so the two lists drift by
one. A human reply that follows the real answer is then paired with the
next answer's (future) timestamp, `ts - last_resp.ts` goes negative, and
since a negative gap is always below the read-fraction threshold, a
spurious `fast_skip` is recorded. That inflates `fast_skip_rate`, which
feeds `pressure`, which lowers the recommended verbosity level.

The user side of `_ordered_events` already replicates its parse-site
filter (`_human_text(...) is None -> continue`); only the assistant side
was missing the equivalent guard. That asymmetry is the bug.

## Fix

In `_ordered_events`, compute `words`/`out_tok` for the assistant line
the same way `_parse_session` does and only consume a response when
`words > 0 or out_tok > 0`, keeping the two functions in lockstep.

Closes #

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `headroom/learn/verbosity.py`: `_ordered_events` applies the `words >
0 or out_tok > 0` guard on the assistant branch before consuming a
response, with a comment explaining the desync.
- `tests/test_verbosity_learn.py`: add `_empty_assistant` helper and
`test_empty_assistant_message_does_not_desync_fast_skip` (an empty
assistant turn before a real answer + a slow reply must not record a
fast skip).
- `CHANGELOG.md`: Bug Fixes entry.

## Testing

- [x] Unit tests pass (`uv run --extra dev pytest
tests/test_verbosity_learn.py::TestSignalExtraction::test_empty_assistant_message_does_not_desync_fast_skip
-q`)
- [x] Linting passes (`uvx ruff@0.15.17 check
headroom/learn/verbosity.py tests/test_verbosity_learn.py
headroom/memory/factory.py`)
- [x] Type checking passes (`uvx mypy==1.20.2
headroom/memory/factory.py`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
$ uvx ruff@0.15.17 check headroom/learn/verbosity.py tests/test_verbosity_learn.py
All checks passed!
$ python -m py_compile headroom/learn/verbosity.py tests/test_verbosity_learn.py
OK
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.12, `uvx ruff@0.15.17`. Importing
`headroom` pulls in the torch/transformers stack and a full `pytest`
gets OOM-killed on this box, so I verified the alignment with a
dependency-free script that models the parse-site filter, the old vs new
`_ordered_events` consume, and the resulting human-to-response pairing,
and left the full pytest to CI.
- Exact command / steps: built an event stream `[empty assistant, real
answer #1, fast human reply, real answer headroomlabs-ai#2, reply]`, computed the
response list from the parse filter, then walked the old (unfiltered)
and new (filtered) consume to find the gap between the first human and
the response paired before it.
- Observed result: old consume pairs the reply with answer headroomlabs-ai#2 (a future
timestamp) -> gap `-8` (spurious fast_skip); new consume keeps alignment
and pairs it with answer #1 -> gap `+1`. The new test builds a session
with an empty assistant turn and a genuinely slow reply and asserts
`fast_skips == 0`.
- Not tested: a real Claude Code transcript end to end; full local
`pytest` deferred to CI (OOM, per above).

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

Merged current `main` to pick up the repository-wide mypy cache-key
annotation fix, then verified the focused regression locally. the change
adds the existing parse-site filter to one branch of a pure file-parsing
function, verified by the standalone alignment proof and the new
regression test for CI.

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
br4vesirrobin pushed a commit that referenced this pull request Jul 29, 2026
)

## Description

`main` CI is red on three independent test failures. All three are
**test-side** bugs (stale cache, semantic merge conflict, stale mock) —
no product code regressed. Each test passed in isolation but failed on
`main`, and each also blocks the `chore: release main` PR (headroomlabs-ai#1923).

Closes #

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- **`test_l2_appends_transform_label`** — `tool_desc_max_chars()`
memoises into a module global. An earlier test in shard 1 reads it with
the env unset, pinning the cache to `0`, so this test's
`setenv("HEADROOM_TOOL_DESC_MAX_CHARS=20")` was swallowed (`assert 0 ==
20`). Reset the cache before reading and after, mirroring the sibling
`test_l2_skips_label_when_disabled`.
- **`test_dashboard_uses_cached_stats_and_lazy_history_feed_polling`** —
semantic merge conflict: headroomlabs-ai#2198 (persist lifetime metrics) intentionally
retired the session-card `Filtered (lifetime)` row and moved
CLI-filtering lifetime into the history tab as `Lifetime Saved`, while
the assertion from headroomlabs-ai#1433 still checked the old string. Assert the
current `Lifetime Saved` label.
- **`test_smart_crusher_log_fallback_runs_for_valid_json`** — stale
mock: headroomlabs-ai#1857 made token counting whitespace-aware, so the router now
rates the JSON above the naive `len(content.split())==8` the no-op
kompress mock reported, making it look like a saving and
short-circuiting before the Log fallback. Mock now reports
`_estimate_tokens(content)` to match the router.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)

### Test Output

```text
$ pytest tests/test_anthropic_compaction_transforms.py \
         tests/test_proxy_dashboard_stats_cache.py \
         tests/test_transforms_content_router.py -q
78 passed, 1 skipped in 12.14s

$ ruff check <the three files>
All checks passed!
$ ruff format --check <the three files>
3 files already formatted
```

## Real Behavior Proof

- Environment: local `.venv`, Python 3.12.6, pytest 9.0.2 (same three
tests that fail on the `main` CI shards 1/3/4).
- Exact command / steps: ran the three previously-failing tests by node
id — all pass. Reproduced the shard-isolation failure for #1 by calling
`tool_desc_max_chars()` with the env unset (cache → 0) before the test,
confirmed the reset makes it pass.
- Observed result: 3/3 target tests pass; 78 passed / 1 skipped across
the three full files.
- Not tested: full suite (unchanged product code); CI shards will re-run
on this PR.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Additional Notes

`mypy headroom` (the CI-enforced scope) is unaffected — these edits
touch only `tests/`, which CI does not type-check. Once this lands on
`main`, the `chore: release main` PR (headroomlabs-ai#1923) drops to just the
`test_root_server_json_matches_builder` failure, which is the release
version-bump `server.json` regen (not a code bug).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: needs author action Pull request body or readiness checklist still needs author updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants