mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
239 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
34a5517562
|
chore: release 0.36.5 (#3214)
🤖 I have created a release *beep* *boop* --- ## [0.36.5](https://github.com/headroomlabs-ai/headroom/compare/v0.36.4...v0.36.5) (2026-08-22) ### Bug Fixes * **codex:** detect ChatGPT auth from id_token claims so wrap/init emit requires_openai_auth ([#3212](https://github.com/headroomlabs-ai/headroom/issues/3212)) ([ |
||
|
|
91186b40d8
|
chore: release 0.36.4 (#3189)
🤖 I have created a release *beep* *boop* --- ## [0.36.4](https://github.com/headroomlabs-ai/headroom/compare/v0.36.3...v0.36.4) (2026-08-22) ### Bug Fixes * **dashboard:** pin MIME types for the vendored static assets ([#3193](https://github.com/headroomlabs-ai/headroom/issues/3193)) ([ |
||
|
|
87e71dd100
|
chore: release 0.36.3 (#3188)
🤖 I have created a release *beep* *boop* --- ## [0.36.3](https://github.com/headroomlabs-ai/headroom/compare/v0.36.2...v0.36.3) (2026-08-21) ### Bug Fixes * **proxy/responses:** lift Codex >= 0.149.0 additional_tools into top-level tools ([#3186](https://github.com/headroomlabs-ai/headroom/issues/3186)) ([ |
||
|
|
5e0ce242e9
|
chore: release 0.36.2 (#3157)
🤖 I have created a release *beep* *boop* --- ## [0.36.2](https://github.com/headroomlabs-ai/headroom/compare/v0.36.1...v0.36.2) (2026-08-21) ### Bug Fixes * **copilot:** bind the minted token to the integration ID we forward ([#3164](https://github.com/headroomlabs-ai/headroom/issues/3164)) ([ |
||
|
|
f7e5d37f52
|
deps: bump ai from 6.0.149 to 7.0.59 in /docs (#2277)
Bumps [ai](https://github.com/vercel/ai/tree/HEAD/packages/ai) from 6.0.149 to 7.0.59. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vercel/ai/releases">ai's releases</a>.</em></p> <blockquote> <h2>ai@6.0.253</h2> <h3>Patch Changes</h3> <ul> <li>d91d30b: Preserve reasoning block IDs from UI message streams on reasoning UI parts.</li> <li>Updated dependencies [0ec239b] <ul> <li><code>@ai-sdk/gateway</code><a href="https://github.com/3"><code>@3</code></a>.0.172</li> </ul> </li> </ul> <h2>ai@6.0.252</h2> <h3>Patch Changes</h3> <ul> <li>2f96d3f: Allow providers without reranking model support to satisfy the <code>Provider</code> type.</li> <li>afb1965: Propagate errors thrown by the Chat <code>onFinish</code> callback to the initiating request.</li> <li>Updated dependencies [18b0965]</li> <li>Updated dependencies [451d2c3] <ul> <li><code>@ai-sdk/gateway</code><a href="https://github.com/3"><code>@3</code></a>.0.171</li> </ul> </li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/vercel/ai/blob/main/packages/ai/CHANGELOG.md">ai's changelog</a>.</em></p> <blockquote> <h2>7.0.59</h2> <h3>Patch Changes</h3> <ul> <li>Updated dependencies [401a4ba]</li> <li>Updated dependencies [7af9646] <ul> <li><code>@ai-sdk/provider-utils</code><a href="https://github.com/5"><code>@5</code></a>.0.26</li> <li><code>@ai-sdk/gateway</code><a href="https://github.com/4"><code>@4</code></a>.0.47</li> </ul> </li> </ul> <h2>7.0.58</h2> <h3>Patch Changes</h3> <ul> <li> <p>72ad23f: Respect ToolLoopAgent timeouts configured in agent settings.</p> </li> <li> <p>ad6a650: feat(video): allow <code>aspectRatio: 'adaptive'</code> on <code>generateVideo</code></p> <p>Some video models derive the output ratio from the input and reject explicit <code>{width}:{height}</code> values — BytePlus Seedance 2.5 does this for first-frame, first-and-last-frame, editing, and extension tasks. <code>aspectRatio</code> on <code>VideoModelV3CallOptions</code>, <code>VideoModelV4CallOptions</code>, and <code>experimental_generateVideo</code> is now <code>`${number}:${number}` | 'adaptive'</code>, so those calls no longer need a type assertion. Support is provider-specific.</p> </li> <li> <p>81cd026: Reduce bundle size by making internal Zod v4 imports tree-shakeable.</p> </li> <li> <p>Updated dependencies [c477556]</p> </li> <li> <p>Updated dependencies [ad6a650]</p> </li> <li> <p>Updated dependencies [81cd026]</p> <ul> <li><code>@ai-sdk/gateway</code><a href="https://github.com/4"><code>@4</code></a>.0.46</li> <li><code>@ai-sdk/provider</code><a href="https://github.com/4"><code>@4</code></a>.0.7</li> <li><code>@ai-sdk/provider-utils</code><a href="https://github.com/5"><code>@5</code></a>.0.25</li> </ul> </li> </ul> <h2>7.0.57</h2> <h3>Patch Changes</h3> <ul> <li>Updated dependencies [1937bef] <ul> <li><code>@ai-sdk/provider-utils</code><a href="https://github.com/5"><code>@5</code></a>.0.24</li> <li><code>@ai-sdk/gateway</code><a href="https://github.com/4"><code>@4</code></a>.0.45</li> </ul> </li> </ul> <h2>7.0.56</h2> <h3>Patch Changes</h3> <ul> <li> <p>25c9120: Expose provider metadata on language-model-call end callbacks and telemetry spans.</p> </li> <li> <p>89080c8: fix (ai/gateway): make retried <code>doStart</code> calls idempotent</p> <p><code>generateVideo</code> retries <code>doStart</code>, which creates a billable generation, so a retry after a lost response could start a second one. It now mints one idempotency token per logical start — outside the retry closure — and forwards it as an <code>idempotency-key</code> header, so a provider that deduplicates (the Vercel AI</p> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href=" |
||
|
|
6928d1932c
|
deps: update mcp requirement from <2.0.0,>=1.28.1 to >=1.28.1,<3.0.0 (#3144)
Updates the requirements on [mcp](https://github.com/modelcontextprotocol/python-sdk) to permit the latest version. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/modelcontextprotocol/python-sdk/releases">mcp's releases</a>.</em></p> <blockquote> <h2>v2.0.0</h2> <h1>MCP Python SDK v2 Stable Release</h1> <p>This is v2.0.0, the stable v2 release of the MCP Python SDK. It supports the 2026-07-28 revision of the Model Context Protocol and serves every earlier revision from the same server. <code>pip install mcp</code> now installs 2.x.</p> <pre lang="bash"><code>pip install "mcp[cli]" # or uv add "mcp[cli]" </code></pre> <h3>Documentation Rewrite</h3> <p>The <a href="https://py.sdk.modelcontextprotocol.io/">documentation</a> has the full tutorial and API reference. Coming from v1? <a href="https://py.sdk.modelcontextprotocol.io/whats-new/">What's new in v2</a> is the tour of what changed and why, and the <a href="https://py.sdk.modelcontextprotocol.io/migration/">migration guide</a> lists every breaking change with before-and-after code.</p> <h3>V1 Maintenance mode</h3> <p><strong>v1.x is in maintenance mode and will only receive security fixes from now on</strong> The 1.x line lives on the <a href="https://github.com/modelcontextprotocol/python-sdk/tree/v1.x"><code>v1.x</code> branch</a>, continues to receive critical bug fixes and security patches, and is documented at <a href="https://py.sdk.modelcontextprotocol.io/v1/">https://py.sdk.modelcontextprotocol.io/v1/</a>. If your project is not ready to migrate, keep a <code><2</code> upper bound on your requirement (for example <code>mcp>=1.28,<2</code>).</p> <h2>Highlights</h2> <h3>One SDK, both protocol eras</h3> <p>v2 speaks the 2026-07-28 revision (stateless requests with no handshake, <code>server/discover</code>, <code>subscriptions/listen</code>, multi-round-trip requests) and still serves every 2025-era client from the same <code>MCPServer</code>, over Streamable HTTP and stdio, with nothing to configure. <code>Client(target)</code> negotiates the version automatically.</p> <h3><code>FastMCP</code> is now <code>MCPServer</code>, and there is a first-class <code>Client</code></h3> <p>The decorator API is unchanged; the low-level <code>Server</code> is rebuilt around a shared dispatcher engine, and one <code>Client</code> object replaces v1's transport-plus-<code>ClientSession</code>-plus-<code>initialize()</code> layering. It connects to a URL, a stdio subprocess, a custom transport, or straight to a server object in memory for tests.</p> <h3>Multi-round-trip requests and resolver dependency injection</h3> <p>At 2026-07-28 the server can no longer call the client, so tools return the question instead. A <code>Resolve(fn)</code> parameter is filled by your function invisibly to the model and can put a question to the user; one tool body serves both eras.</p> <h3>Extension APIs, OpenTelemetry, and a standalone types package</h3> <p>Servers and clients compose protocol extensions through pluggable extension APIs (MCP Apps built in); OpenTelemetry tracing ships on by default; every protocol type is its own package, <code>mcp-types</code> (imported as <code>mcp_types</code>), published in lock-step with <code>mcp</code>.</p> <h3>Hardened stdio and auth</h3> <p>stdio servers keep handler subprocesses and stray prints off the wire, and stdout is diverted to stderr while serving. OAuth adds RFC 9207 issuer validation, the SEP-990 identity-assertion flow, and the client-credentials extension.</p> <h2>Coming from a v2 pre-release</h2> <p>Since the last release candidate: the per-version wire packages are private (<code>mcp_types._v*</code>), <code>mcp.types</code> is a permanent alias for <code>mcp_types</code>, the auth registration request model is split from the registered-client record, cancelled requests are no longer answered, and log notifications are gated on the per-request log-level opt-in at 2026-07-28. Since the betas: <code>Client(cache=False)</code> is now <code>cache=None</code> with <code>CacheConfig()</code> the default; <code>Context.client_id</code>, <code>RFC7523OAuthClientProvider</code>, and <code>OAuthClientProvider(timeout=)</code> are removed; the client-credentials providers take <code>scope=</code>; <code>message_handler</code> receives notifications and exceptions only; <code>FileResource(is_binary=)</code> becomes <code>encoding</code>; <code>MCP_*</code> env vars are gone with <code>pydantic-settings</code>; Streamable HTTP servers reject bodies over 4 MiB with HTTP 413. The migration guide covers all of it.</p> <h2>Known gaps</h2> <p>The tasks extension (SEP-2663) is not part of this release. On the client, the DPoP proof binding (SEP-1932) and the workload-identity <code>jwt-bearer</code> grant are not implemented; both are additive and can land in 2.x.</p> <h2>Feedback</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href=" |
||
|
|
37faf2f247
|
chore: release 0.36.1 (#3152)
## Description Release 0.36.1, generated by Release Please, containing the security fixes from #2207 (WEB-01–07). This updates the changelog and keeps Python, TypeScript SDK, plugin package, marketplace, server, and release metadata versions aligned at 0.36.1. ## Type of Change - [x] Release / version metadata ## Changes Made - Updated the release manifest and generated changelog for 0.36.1. - Synchronized `pyproject.toml`, TypeScript SDK, OpenClaw, OpenCode, agent-hook plugin, marketplace, server, and release metadata versions. - Included the 0.36.1 changelog entry for the security assessment fixes merged in #2207. ## Testing - [x] CI and release validation pass ### Test Output All current required checks are complete and passing, including version sync, package builds, wheel smoke imports, security scans, Python test shards, native wrapper checks, and devcontainer validation. ## Real Behavior Proof - Environment: GitHub Actions release and CI workflows for commit `52c0a0c61dce0af81af3ff73a34efe8b451501cb`. - Observed result: all generated version-bearing files report 0.36.1; build and smoke-import jobs produced and validated the release artifacts. - Not exercised: publishing jobs are intentionally skipped for a pull request and run only after the release receives final human approval and is merged. ## Runtime Rollout Safety - Rollout-managed features: none; this PR packages already-merged behavior. - Stable/default behavior changed: no additional runtime behavior beyond the included, already-reviewed security fixes. - Kill switch / disable path: not applicable to generated release metadata. - Qualification impact: release artifact construction and smoke-import validation are green. - Rollback path: do not merge the release PR, or revert the release commit before publishing. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Release Notes ### Bug Fixes - **security:** address u9up assessment findings (WEB-01–07) (#2207) This PR was generated with Release Please and then its description was expanded to document review and qualification evidence. It still requires final human review; no publishing or merge has been performed. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
bf651c3dc1
|
fix(docker): give :latest exactly one writer (#3154)
## Description Closes #3150. `ghcr.io/headroomlabs-ai/headroom:latest` resolved to the distroless `code-slim` build, whose `import onnxruntime` segfaults on arm64. The proxy imports onnxruntime at startup in cache mode, so the container never bound its port and `headroom deploy` crash-looped (exit 139) on Apple Silicon. @ricwo's report is exceptionally good — it isolates the base image with a copy-`site-packages`-onto-`debian:trixie-slim` experiment, and explicitly retracts an earlier wrong theory about the `cpuid_info` line. I verified the tagging half independently against the live registry: ``` latest sha256:6b34905489e3... <- identical 0.36.0-code-slim sha256:6b34905489e3... <- identical 0.36.0 sha256:bb8e77d01b54... ``` **Root cause, proven from the job log rather than inferred.** `docker/metadata-action` defaults to `latest=auto`, which appends a bare `latest` for any semver release — and its own log line reads `suffixLatest=false`, meaning the per-tag `suffix=` that keeps every other tag variant-scoped never reaches it. All eight variant cells therefore emitted `:latest`, and the last to finish won. From the 0.36.0 `code-slim` cell: ``` latest=auto suffixLatest=false tags: [..."ghcr.io/headroomlabs-ai/headroom:code-slim", "ghcr.io/headroomlabs-ai/headroom:latest"] pushing sha256:fbcbb68... to ghcr.io/headroomlabs-ai/headroom:latest ``` It landed on `code-slim` by scheduling luck. Any of the eight could have won on any release. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) ## Changes Made - **`flavor: latest=false`** on the `docker-manifest` metadata-action. Stops the tag being generated at all, leaving the root-cell promotion step as the single writer of `:latest`. - **A runtime guard** in `Create multi-arch manifest`: if a suffixed variant reaches the push carrying a bare `latest`, the job fails instead of publishing. `VARIANT_NAME` is passed via `env:` rather than spliced inline. - **A test that encodes the missing half of the contract.** `test_docker_latest_promotion_is_owned_by_root_manifest_cell` already existed and passed throughout — it asserted the *intended* writer was the root cell but never the *absence of unintended ones*. The new test asserts exclusivity: `latest=false` is set, no tag rule reintroduces `value=latest`, and the guard runs before anything is pushed. ## Testing - [x] Unit tests pass (`pytest`) ### Test Output ```text tests/test_release_workflows.py 48 passed, 1 skipped, 1 failed The failure is test_no_native_tls_in_wheel_build_tree: FileNotFoundError: [Errno 2] No such file or directory: 'cargo' Pre-existing and environmental — cargo is not installed on this machine; it fails identically on a clean main checkout. ruff check: All checks passed ruff format --check: 1 file already formatted YAML parses; flavor='latest=false', env keys ['IMAGE','DIGEST_DIR','VARIANT_NAME']. ``` ## Real Behavior Proof - Environment: macOS (darwin 25.4.0), worktree off `main`. Live registry queried anonymously via the GHCR token endpoint. - Exact command / steps: (1) resolved `latest`, `0.36.0` and all four variant tags to manifest digests directly from `ghcr.io/v2/.../manifests/*` to confirm the aliasing; (2) pulled the `docker-manifest (code-slim)` job log from the 0.36.0 release run to see which tags that cell actually pushed; (3) applied the fix and ran the workflow test suite; (4) **removed `latest=false` again and re-ran the new test** to confirm it reproduces the bug. - Observed result: `:latest` and `:0.36.0-code-slim` share digest `sha256:6b34905489e3...` while `:0.36.0` is `sha256:bb8e77d01b54...`, exactly as reported. The code-slim job log shows `latest=auto` / `suffixLatest=false` and `pushing ... to ghcr.io/headroomlabs-ai/headroom:latest`. With the fix removed the new test fails on `assert 'latest=false' in ''`; with it restored, it passes. - Not tested: I could not exercise the arm64 segfault or a real multi-arch push from here — no ghcr write credential and no arm64 runner. The tagging fix is verified at the config layer plus the registry evidence above; the end-to-end proof is the re-run described below. ## Runtime Rollout Safety - Rollout-managed feature(s): None. - Minimum rollout channel: n/a - Stable/default behavior changed: Yes, and that is the fix — `:latest` will track the plain Debian-based build instead of whichever variant cell happened to finish last. - Kill switch / disable path: n/a (CI tagging policy). - Unsafe override required: No. - Qualification impact: A variant cell that would publish a bare `latest` now fails the Docker job loudly rather than silently repointing the default tag. - Rollback path: Revert the commit. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Additional Notes **The live `:latest` is still wrong until the images are re-tagged.** Merging this fixes future releases but does not touch the registry. Once merged, run `docker.yml` via `workflow_dispatch` with `version=0.36.0` to rebuild and repoint `:latest` at the plain build. I don't hold a `write:packages` credential, so that step needs a maintainer. **Not fixed here, and it outlives this PR:** the distroless arm64 segfault itself. After this change `:latest` points at the Debian build that works, but `0.36.0-slim` and `0.36.0-code-slim` remain broken on arm64 for anyone selecting them explicitly. @ricwo's evidence points squarely at the distroless base — same wheel, same numpy 2.5.2, same Python 3.13.5, works on `debian:trixie-slim` and segfaults on distroless. That deserves its own issue; the two failures are independent and this one is a release-tagging bug, exactly as the report says. Related but separate, from an earlier audit of this same file: the four bare variants set `RUNTIME_USER = "root"` in `docker-bake.hcl` while `Dockerfile:162` defaults to `nonroot`, and the `runtime-default` (nonroot) bake target is referenced by the docs but by no workflow. Worth its own change. Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local> |
||
|
|
1f96dabc19
|
fix(security): address u9up assessment findings (WEB-01–07) (#2207)
Hardens client-selected upstreams, memory identity resolution, downloaded binary integrity, telemetry import, Docker defaults, Neo4j credentials, and archive extraction. Refreshes the branch against current main and preserves newer same-origin and loopback protections. |
||
|
|
b88b9078d8
|
chore: release 0.36.0 (#3067)
🤖 I have created a release *beep* *boop* --- ## [0.36.0](https://github.com/headroomlabs-ai/headroom/compare/v0.35.0...v0.36.0) (2026-08-20) ### Features * add deterministic runtime rollout controls ([#1490](https://github.com/headroomlabs-ai/headroom/issues/1490)) ([ |
||
|
|
93c474e84b
|
fix(deps): clear the two Rust advisories and make cargo audit blocking (#3121)
## Description
An independent OSV sweep of every locked package in the repo (1,463
across PyPI, crates.io and npm) surfaced three RUSTSEC advisories that
**no gate was reporting**:
| advisory | package | status |
|---|---|---|
| RUSTSEC-2026-0258 (GHSA-q83h-524g-xf6h) | h2 0.4.15 | fixed here →
0.4.16 |
| RUSTSEC-2026-0204 | crossbeam-epoch 0.9.18 | fixed here → 0.9.20 |
| RUSTSEC-2024-0436 | paste 1.0.15 | unmaintained, **no patched version
exists** |
**h2 is the one that matters.** It accepted and queued empty DATA frames
without limit; a peer that never drains a stream drives unbounded memory
growth, or a panic when the length overflows. It is not a corner of the
tree — it reaches the published wheel (`hf-hub -> headroom-core ->
headroom-py`) and the entire axum/reqwest/aws-config surface of
`headroom-proxy`.
**Why none of this was visible** is the more important half of this PR.
The `audit` job was already correct in one respect I initially misread —
the `rust-changes` job reports `rust=true` for `schedule`, so it *does*
run nightly rather than only on Rust changes. The actual defect is that
`cargo audit` was `continue-on-error: true`. It has been faithfully
reporting findings into a green run that nobody looks at.
Two of the three are also invisible to Dependabot entirely:
`crossbeam-epoch` and `paste` are RUSTSEC-only with no GHSA, so the
advisory database GitHub scans does not contain them. This job is their
only possible coverage.
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
## Changes Made
- `Cargo.lock`: `h2` 0.4.15 → 0.4.16, `crossbeam-epoch` 0.9.18 → 0.9.20.
Version + checksum only, 4 lines each way.
- `.github/workflows/rust.yml`: dropped `continue-on-error: true` from
the `cargo audit` step. `cargo deny check licenses` is deliberately left
soft-fail — `deny.toml` documents itself as intentionally permissive for
now, and tightening license policy is a separate decision.
- `.cargo/audit.toml` (new): lists `RUSTSEC-2024-0436` as accepted, with
the reason. Path matters — cargo-audit reads `.cargo/audit.toml`; a
root-level `audit.toml` is silently ignored.
`paste` is unmaintained rather than vulnerable, and there is nothing to
move to. It arrives via `tokenizers -> paste` and `rav1e -> paste`, both
under `fastembed`, so it is not actionable at our layer. Worth
revisiting when `tokenizers` adopts `pastey`.
## Testing
- [x] Manual testing performed
### Test Output
```text
Checksums verified against the real crates.io tarballs, not just the API field:
OK h2 0.4.16 (173331 bytes)
lock : a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27
real : a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27
OK crossbeam-epoch 0.9.20 (47545 bytes)
lock : 2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f
real : 2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f
Dependency-set equality (crates.io API, kind=normal):
h2 0.4.15 -> 0.4.16 : 11 deps before, 11 after, identical
crossbeam-epoch 0.9.18 -> .20: 2 deps before, 2 after, identical
```
## Real Behavior Proof
- Environment: macOS (darwin 25.4.0), worktree off `main` @ `
|
||
|
|
ac8646aa3c
|
fix(ci): scope the release credential and stop persisting it to disk (#3062)
## Description `RELEASE_PLEASE_TOKEN` is currently a maintainer's personal PAT. It bypasses branch and tag protection on `main` (`release-please.yml` says so in its own comment), and forging a tag with it fires `release.yml` and `docker.yml` on `release: published`, which publish to PyPI, npm and GHCR. If it is a classic token with `repo` scope it is also valid against every other repository that account can reach. `release-metadata-sync.yml` made that credential readable on the runner. `actions/checkout` defaults to `persist-credentials: true`, writing the token into `.git/config`, and the very next step runs `scripts/version-sync.py` **from the checked-out branch**. The trigger is a push to the glob `release-please--branches--**`, which is not a protected namespace, so a principal with push access could land a modified `version-sync.py` and read it. Closes #2955. ## 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 ## Changes Made - Both release workflows now prefer a GitHub App installation token — scoped to this repository, expiring in an hour — over the PAT, via `actions/create-github-app-token@v3`. - The minting step is gated on `vars.RELEASE_APP_ID` and marked `continue-on-error`, so an unconfigured app falls through to the existing `PAT -> GITHUB_TOKEN` chain and nothing breaks today. - `release-metadata-sync.yml`'s checkout no longer persists credentials, and no longer receives a token at all. - The final push supplies the credential through the step's own `env` and an explicit remote URL, so it is never on disk while branch-supplied code runs. ## Testing - [x] Unit tests pass - [x] Linting passes (ruff check + format on the test file) - [ ] Type checking passes — N/A (YAML + test only) - [x] New tests added for new functionality ### Test Output ```text $ .venv/bin/python -m pytest tests/test_release_workflows.py -q 1 failed, 44 passed, 1 skipped in 0.23s ``` The single failure is `test_no_native_tls_in_wheel_build_tree`, which shells out to `cargo`. It reproduces identically on unmodified `main` on this machine (no Rust toolchain installed) and is unrelated to this change. New tests only: ```text $ .venv/bin/python -m pytest tests/test_release_workflows.py -q -k "persist_credentials or scoped_app_token" 3 passed, 46 deselected in 0.18s ``` Against the parent commit: ```text FAILED test_metadata_sync_does_not_persist_credentials_for_branch_supplied_code FAILED test_release_workflows_prefer_scoped_app_token[release-please.yml-release-please] FAILED test_release_workflows_prefer_scoped_app_token[release-metadata-sync.yml-sync] 3 failed, 46 deselected ``` ## Real Behavior Proof - Environment: macOS 15 (darwin 25.4.0), Python 3.12.13; workflows parsed with PyYAML, not executed on a runner. - Exact command / steps: parse both workflow files and assert (a) every `actions/checkout` step sets `persist-credentials: false` and receives no `token`, (b) exactly one gated `create-github-app-token` step exists per workflow, and (c) every credential consumer places `steps.app-token.outputs.token` ahead of `secrets.RELEASE_PLEASE_TOKEN` in its fallback chain. - Observed result: all three assertions pass on this branch and fail on the parent commit. Both files remain valid YAML. - **Not tested — important:** none of this has executed on a GitHub runner. I have not minted a real installation token, not confirmed the app-token step's `continue-on-error` fallback behaves as expected when `vars.RELEASE_APP_ID` is unset, and not performed a real push with the explicit-remote-URL form. The first live release run is the real test. ## Runtime Rollout Safety - Rollout-managed feature(s): none. - Minimum rollout channel: N/A. - Stable/default behavior changed: no, unless `vars.RELEASE_APP_ID` is set — without it both workflows resolve to exactly today's credential chain. - Kill switch / disable path: unset `vars.RELEASE_APP_ID` to fall back to the PAT. - Unsafe override required: none. - Qualification impact: none. - Rollback path: revert this commit. ## 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] My changes generate no new warnings - [x] I have added tests that prove my fix is effective - [x] New and existing unit tests pass locally with my changes ## Additional Notes **This narrows blast radius; it does not make the trigger safe on its own.** For an `on: push` workflow GitHub reads the workflow file from the pushed ref, so a principal with push access can still edit this file on their branch. The durable fix is the scoped app token *plus revoking the personal PAT* — the revocation is a console action and is deliberately not in this commit. **Two repo settings are required to actually complete #2955**, and neither can land in git: ``` vars.RELEASE_APP_ID (repository variable) secrets.RELEASE_APP_PRIVATE_KEY (repository secret) ``` Until those exist this PR is a no-op on behavior and a defense-in-depth improvement on the `persist-credentials` path only. Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local> |
||
|
|
ddd2a259ec
|
fix(install): consolidate Windows fallback and cleanup safety (#2980)
## Description Consolidates two fully reviewed installation-safety fixes whose original PRs can no longer merge under current branch protection: Windows persistent-service deployments need a supported Task Scheduler fallback, and legacy context-tool cleanup must never delete user-owned RTK/lean-ctx artifacts. Closes #2552 Closes #2817 Supersedes #2600 and #2828 while preserving their authors' commits and review-driven corrections. ## 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) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Convert Windows `persistent-service` plans to the supported `persistent-task` supervisor and make the fallback explicit in CLI output. - Restrict context-tool cleanup to artifacts proven to live under Headroom's managed directory. - Recognize wrapped, relative, and platform-specific managed commands without accepting prefixed/path-boundary lookalikes. - Scope cleanup completion state correctly across projects and alternate agent homes. - Stamp cleanup complete only after all managed remnants are settled. - Preserve the original focused regression suites and behavior-proof artifact. ## 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 -q tests/test_install/test_planner.py tests/test_install/test_supervisors.py tests/test_cli/test_install_cli.py tests/test_context_tool_cleanup.py tests/test_cli/test_unwrap_claude.py 135 passed in 0.45s $ uv run ruff check <changed Python and test files> All checks passed! $ uv run ruff format --check <changed Python and test files> 8 files already formatted ``` ## Real Behavior Proof - Environment: macOS arm64 for consolidated current-main validation; the Windows fallback source PR was independently validated on Windows and includes its captured verification artifact. - Exact command / steps: run the planner, supervisor, install CLI, cleanup provenance, and unwrap suites on the rebased combined branch. - Observed result: 135/135 focused tests pass. Windows service requests resolve to `persistent-task`; cleanup rejects user-owned and path-prefix lookalikes while removing managed artifacts. - Not tested: a fresh privileged Windows host deployment in this local pass; #2600's accepted review contains the Windows-specific proof. ## Runtime Rollout Safety - Rollout-managed feature(s): Install supervisor selection and one-time legacy cleanup. - Minimum rollout channel: Stable/default; both prevent currently destructive or nonfunctional install paths. - Stable/default behavior changed: Windows service requests use Task Scheduler; cleanup requires managed provenance. - Kill switch / disable path: Select `persistent-task` explicitly; cleanup remains bounded by its completion stamp and provenance checks. - Unsafe override required: No. - Qualification impact: Windows native install and wrap/unwrap cleanup suites. - Rollback path: Revert this PR, restoring the two pre-fix behaviors. ## 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 - [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 did **not** edit `CHANGELOG.md` — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Screenshots (if applicable) The Windows verification artifact from #2600 is retained at `.github/pr-images/issue-2552-windows-fallback-verification.png`. ## Additional Notes This is intentionally an installation-safety batch rather than two replacement PRs. Original commit authorship is preserved, and the combined diff was applied cleanly to current `main` after #2832 and #1628 landed. --------- Co-authored-by: Inference1 <68734681+Inference1@users.noreply.github.com> Co-authored-by: Dennis Alexis Valin Dittrich <dd+github@dr-dittrich.de> |
||
|
|
e269afb935
|
fix(ci): unjam release and Docker publishing (#2958)
## Description
Fixes two release-automation defects exposed by the 0.35.0 release:
1. Release Please grouped the single package into a PR titled `chore:
release main`, which could not be matched back to the `headroom-ai`
component/version and therefore never emitted the release event that
starts PyPI publishing.
2. Docker manifest jobs downloaded digest artifacts with overlapping
variant globs. For example, `digests-code-*` also selected code-nonroot,
code-slim, and code-slim-nonroot artifacts, yielding eight markers where
exactly two were required.
## 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
- Route the single root package through Release Please's normal
versioned-PR path.
- Preserve both `${component}` and `${version}` in generated release PR
titles.
- Download Docker amd64 and arm64 digest artifacts by exact name instead
of an overlapping variant glob.
- Add regression assertions for both Release Please title matching and
Docker artifact isolation.
## 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
python -m pytest tests/test_release_workflows.py -q
44 passed in 2.29s
pre-commit hooks:
Sync plugin versions........................Passed
Verify Ruff version alignment...............Passed
check for merge conflicts...................Passed
ruff (legacy alias).........................Passed
ruff format.................................Passed
mypy........................................Passed
```
## Real Behavior Proof
- Environment: GitHub Actions release runs 31653073583 and 31664975515,
`main` at the 0.35.0 release merge (`93f2d7a2`).
- Exact command / steps: Inspected the Release Please rerun and each
failed Docker manifest job; enumerated the digest artifacts downloaded
by their configured patterns.
- Observed result: Release Please logged `There are untagged, merged
release PRs outstanding - aborting`. Docker's `slim`, `code`, and
`code-slim` manifest cells found 4, 8, and 4 digest markers respectively
instead of 2 because their prefix globs included related variants. The
new Docker workflow requests `digests-<variant>-amd64` and
`digests-<variant>-arm64` by exact name.
- Not tested: A synthetic release was not published because registry
versions/tags are irreversible. Both configuration invariants are
covered by regression tests, and GitHub's PR workflow validation runs
against this branch.
## 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 did **not** edit `CHANGELOG.md` — it is generated by
release-please from my Conventional Commit PR title (a CI guard enforces
this)
## Screenshots (if applicable)
N/A — workflow configuration and regression-test changes only.
## Additional Notes
- Documentation is N/A: these are internal release workflow corrections
with no user-facing command or API changes.
- The 0.35.0 Python release recovery is proceeding separately through
the existing wheel smoke-test and PyPI publication gates.
- The already-started 0.35.0 Docker run used the old workflow from tag
commit `
|
||
|
|
3077ac81e8
|
feat: add deterministic runtime rollout controls (#1490)
## Description Establish one centrally resolved, observable, deterministic, versioned runtime rollout-control mechanism for Headroom. Runtime rollout controls which behaviors an already-built artifact may expose; it does not select or qualify a Headroom release/version. ## Type of Change - [x] New feature (non-breaking change that adds functionality) - [x] Bug fix (non-breaking change that fixes rollout enforcement regressions) - [x] Documentation update - [x] Code refactoring (no functional changes) ## Changes Made - Added `RolloutChannel`, `HEADROOM_ROLLOUT_CHANNEL`, `--rollout-channel`, and a versioned immutable `RolloutSnapshot` shared by Python configuration boundaries. - Added schema/policy versions, canonical registry and snapshot SHA-256 identities, per-feature decision reasons, disable precedence, unsafe qualification poisoning, strict CLI validation, and fail-closed environment handling. - Added `headroom rollout status --json`, Python `/stats.rollout`, and Rust `/rollout/status` runtime provenance. - Added equivalent Rust snapshot semantics and shared Python/Rust policy vectors while retaining language-specific feature registries. - Enforced rollout policy at alternate Python server composition roots so `HEADROOM_READ_MATURATION=1` cannot bypass its beta gate. - Preserved typed rollout snapshots across multi-worker serialization with schema, policy, registry, snapshot-digest, type, and feature-name validation. - Made loopback runtime output-shaper updates replace the immutable snapshot atomically for request readers, retain explicit request/disable provenance, preserve channel and kill-switch precedence, invalidate cached stats, and return the effective rollout decision. - Made `headroom learn --verbosity --apply` report a channel-blocked update instead of claiming the shaper is live. - Made explicit CLI feature flags fail loudly when their current channel blocks them. - Made persistent interceptor installation select canary automatically, or reject an explicitly insufficient channel unless the break-glass override is set. - Updated architecture, proxy, rollout, learn, and output-shaper documentation with required channels and hot-reload semantics. ## Testing - [x] Unit tests pass - [x] Linting passes (`ruff check .` and `ruff format --check .`) - [x] Type checking passes (`mypy headroom --ignore-missing-imports`) - [x] New regression tests added for every corrected behavior - [x] Rust tests and production-target Clippy pass - [x] Documentation build passes ### Test Output ```text Focused rollout coverage suite 57 passed; headroom.rollout + rollout CLI: 98% coverage Affected proxy/rollout/transform/governance suites 222 passed; 0 failed Final changed regression suites 100 passed; 0 failed Cross-module hot-reload isolation regression 6 passed; 0 failed cargo test -p headroom-core -p headroom-proxy --quiet headroom-core: 924 passed; 1 ignored headroom-proxy and integration suites: all passed cargo clippy -p headroom-core -p headroom-proxy --lib --bins -- -D warnings cargo fmt --all -- --check ruff check . ruff format --check . mypy headroom --ignore-missing-imports git diff --check All passed cd docs && npm run build Compiled successfully; 164 static pages generated ``` The unsharded Windows-only CI selection exposed unrelated baseline failures, principally the existing `sqlite:///C:\\...` URL parser producing an invalid `\\C:\\...` path. At commit ` |
||
|
|
93f2d7a2da
|
chore: release main (#2792)
🤖 I have created a release *beep* *boop* --- <details><summary>0.35.0</summary> ## [0.35.0](https://github.com/headroomlabs-ai/headroom/compare/v0.34.0...v0.35.0) (2026-08-12) ### Features * **beacon:** allowlist the routing summary key ([#2818](https://github.com/headroomlabs-ai/headroom/issues/2818)) ([ |
||
|
|
78591545ce
|
fix: publish headroom-opencode in release workflow (#2372)
## Description `headroom-opencode` is documented as an npm package, but the release workflow never published it, so installs failed with a registry 404 even though the plugin source already lived under `plugins/opencode`. This wires the existing package into the npm release path, keeps its version synced with root releases, and adds release guards for the new package. Closes #76. ## 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) - [x] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - added `headroom-opencode` to the npm release workflow, including release-version stamping and `headroom-ai` dependency rewrite before publish - added `plugins/opencode/package.json` to release-please and local version-sync guards - synced the source opencode package version to the current release line and documented the new npm package in the release docs - added focused release workflow and version-sync tests for the opencode package - aligned the two failing dashboard Playwright tests with the current Session/Lifetime split and `/stats-lifetime` fixture contract ## Testing - [x] Unit tests pass (`uv run pytest scripts/tests/test_version_sync.py -q`, `uv run pytest tests/test_release_workflows.py -q -k 'publish_npm_rewrites_opencode_dependency_after_version_and_before_publish or opencode_source_dependency_matches_lockfile_registry_range or release_please_manifest_config_consistency'`) - [x] Unit tests pass (`uv run pytest tests/test_dashboard_cache_lifetime_playwright.py tests/test_dashboard_cache_ttl_playwright.py -q`) - [x] Linting passes (`uv run ruff check scripts/verify-versions.py scripts/version-sync.py scripts/tests/test_version_sync.py tests/test_release_workflows.py`) - [ ] Type checking passes (`uv run mypy headroom`) - [x] New tests added for new functionality when applicable - [x] Manual testing performed ### Test Output ```text $ uv run pytest scripts/tests/test_version_sync.py -q 8 passed, 1 warning in 0.51s $ uv run pytest tests/test_release_workflows.py -q -k 'publish_npm_rewrites_opencode_dependency_after_version_and_before_publish or opencode_source_dependency_matches_lockfile_registry_range or release_please_manifest_config_consistency' 2 passed, 38 deselected, 1 warning in 0.07s $ uv run pytest tests/test_dashboard_cache_lifetime_playwright.py tests/test_dashboard_cache_ttl_playwright.py -q 4 passed, 1 warning in 4.04s $ uv run ruff check scripts/verify-versions.py scripts/version-sync.py scripts/tests/test_version_sync.py tests/test_release_workflows.py All checks passed! $ npm ci && npm run build (plugins/opencode) Build success; dist/index.js, dist/entry.opencode.js, and DTS outputs emitted ``` ## Real Behavior Proof - Environment: Windows, Python 3.11.15, Node v24.15.0, npm 11.16.0 - Exact command / steps: inspected `.github/workflows/release.yml`, updated the npm publish path for `plugins/opencode`, aligned the two failing dashboard Playwright tests with the current Session/Lifetime split, then ran the focused pytest commands above plus `npm ci && npm run build` in `plugins/opencode` - Observed result: the release workflow now versions and publishes `headroom-opencode`, release-please and version-sync track `plugins/opencode/package.json`, the dashboard tests now fetch durable cache and setup-url data from the Lifetime view, and the opencode package still builds locally from source - Not tested: GitHub Package Registry publish ## 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 - [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 `CHANGELOG.md` is unchanged because release-please owns changelog generation here. --------- Co-authored-by: JD Davis <mxjerrett@gmail.com> |
||
|
|
5568d738af
|
fix(ci): publish latest from the root Docker manifest (#2252)
## Description A successful root Docker image can miss `:latest` when any optional variant manifest fails. The release workflow currently gates the standalone `promote-latest` job on the aggregate `docker-manifest` matrix, so one sibling failure skips promotion even when the signed root amd64+arm64 manifest exists. This moves `:latest` promotion into the successful root manifest cell. Optional variant failures remain visible and continue to fail their jobs, but they no longer suppress the image used by `headroom install`, which defaults to `ghcr.io/headroomlabs-ai/headroom:latest`. Refs #1583 ## 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 - Publish `:latest` from the root `docker-manifest` matrix cell after its versioned multi-architecture manifest is created and signed. - Remove the aggregate `promote-latest` dependency that allowed unrelated variant failures to suppress publication. - Keep all existing root, slim, code, and nonroot variants. - Preserve native linux/amd64 and linux/arm64 manifest assembly. - Add a focused workflow-contract regression test. ## Testing - [x] Unit tests pass (`uv run pytest tests/test_release_workflows.py -q -k "docker or latest"`) - [x] Linting passes (`uv run ruff check tests/test_release_workflows.py`) - [x] Formatting passes (`uv run ruff format --check tests/test_release_workflows.py`) - [x] New tests added for new functionality - [ ] Manual testing performed ### Test Output ```text Focused checks pass: `5 passed, 34 deselected` for `uv run pytest tests/test_release_workflows.py -q -k "docker or latest"`; the full test file has one unrelated Windows `FileNotFoundError` in `test_no_native_tls_in_wheel_build_tree` because its external command is unavailable. `uv run ruff check tests/test_release_workflows.py` and `uv run ruff format tests/test_release_workflows.py --check` pass. The repository-wide format check reports eight pre-existing files outside this target. Proof report: `D:\Repos\.claude\pr-sweep\headroom-PR-TARGET-1583-PROOF.md`. ``` ## Real Behavior Proof - Environment: Windows, Python managed by `uv`, repository workflow-contract tests; production publication owned by GitHub Actions and GHCR. - Exact command / steps: run the focused release-workflow tests; after merge, inspect the next Docker release run and execute `docker buildx imagetools inspect ghcr.io/headroomlabs-ai/headroom:latest` without registry login. - Observed result: local workflow-contract proof passes for root-owned promotion and both native architecture inputs; live GHCR publication remains unverified until the next release. - Not tested: production GHCR publication before merge. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [ ] My code follows the project's style guidelines - [ ] I have performed a self-review of my code - [x] Workflow comments explain the non-obvious root-only promotion boundary - [x] Documentation outside the changelog is unchanged because the CLI image reference is already correct - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective - [x] New and existing focused tests pass locally with my changes ## Screenshots (if applicable) Not applicable. ## Additional Notes Release run https://github.com/headroomlabs-ai/headroom/actions/runs/28404020512 demonstrated the cascade: the root manifest succeeded, a nonroot manifest failed during Buildx setup, and `promote-latest` was skipped. PR CI can prove the workflow dependency and architecture-preservation contracts. GHCR availability and anonymous package visibility require the next production release plus an unauthenticated registry inspection. |
||
|
|
702dbc5902
|
fix(opencode): ship the transport hook-shim so wheel installs route Node child traffic
## Description The OpenCode transport plugin injects `NODE_OPTIONS=--import=<...>/hook-shim/handler.js` into every spawned Node child so its `fetch`/`http` traffic routes through the proxy (`transport.ts` wraps those globals only in the plugin's own process; a spawned `npx` MCP server or `tokensave serve` is a fresh process). That shim was never shipped in the wheel: - Only `headroom/providers/opencode/_dist/entry.opencode.js` is committed and packaged. - The shim source at `plugins/opencode/hook-shim/handler.js` imports the non-bundled `../dist/index.js`, which a pip install (no `node_modules`) cannot resolve. Before #2806, the missing file crashed every Node MCP under `headroom wrap opencode` with `ERR_MODULE_NOT_FOUND` at the ESM loader, before the stdio handshake. #2806 added an `existsSync` guard so the loader is not injected when the shim is absent, which stopped the crash but left child-process routing silently disabled for all wheel installs (#2850). This ships the shim. It builds a self-contained variant in the standalone tsup config (`src/hook-shim.ts`, with the transport bundled inline like the entry, since site-packages has no `node_modules`), and commits it to `headroom/providers/opencode/hook-shim/handler.js` -- the sibling of `_dist/` that `transport.ts`'s `shimImportSpecifier()` resolves via `../hook-shim/handler.js`. maturin packages every file under `headroom/`, so the wheel now carries it, and `existsSync` finds it, so the loader routes spawned Node children again. Fixes #2850 ## 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 - `plugins/opencode/src/hook-shim.ts` (new): self-contained Node `--import` loader that installs the transport from the inlined `./transport.js`. - `plugins/opencode/tsup.standalone.config.ts`: add `hook-shim/handler` as a second standalone entry. - `headroom/providers/opencode/hook-shim/handler.js` (new): the committed self-contained shim (output of `npm run build:standalone`), shipped by maturin. - `.github/workflows/opencode-plugin.yml`: byte-compare the committed shim against a fresh build (mirrors the existing `entry.opencode.js` guard), and add the shim path to the workflow triggers. - `tests/test_providers_opencode_plugin_path.py`: added `test_hook_shim_is_committed_next_to_the_entry_bundle` asserting the shim ships as a sibling of `_dist/` and is the self-contained build. ## Testing - [x] Unit tests pass (`pytest` + `vitest`) - [x] Type checking passes (`tsc --noEmit`) - [x] New tests added for new functionality - [x] Committed shim rebuilt and byte-matches the standalone build - [ ] Manual testing performed ### Test Output ```text # Fail-before (shim removed from the package): tests/test_providers_opencode_plugin_path.py::test_hook_shim_is_committed_next_to_the_entry_bundle FAILED # Pass-after: tests/test_providers_opencode_plugin_path.py tests/test_providers_opencode_install.py tests/test_providers_opencode_config.py 49 passed, 1 pre-existing failure # the 1 failure (test_build_launch_env_with_project) fails identically on pristine main: # a Windows path-escaping quirk in OPENCODE_CONFIG_CONTENT, unrelated to this diff. # TypeScript: npm run typecheck (clean), npm test -> 14 passed # Standalone build: entry.opencode.js byte-unchanged vs the committed blob; # dist-standalone/hook-shim/handler.js cmp-matches the committed shim. # Shim runtime sanity (node): # with HEADROOM_OPENCODE_TRANSPORT_PROXY_URL set -> loads, exit 0, wraps globalThis.fetch # without it -> throws "loaded without HEADROOM_OPENCODE_TRANSPORT_PROXY_URL", exit 1 ``` ## Real Behavior Proof - Environment: Windows 11, Node v24.11.0, npm 11.5.2, tsup 8.5.1 / esbuild 0.28.1 (pinned via `npm ci`), Python 3.12.11, pytest 9.1.1, ruff 0.15.17. - Exact command / steps: confirmed `transport.ts` resolves `../hook-shim/handler.js` next to the loaded entry (so the wheel needs it at `providers/opencode/hook-shim/handler.js`), that the current wheel ships only `_dist/entry.opencode.js`, and that maturin packages every file under `headroom/`. Added the standalone shim entry, ran `npm run typecheck` and `npm test` (clean), `npm run build:standalone`, verified `entry.opencode.js` is byte-identical to the committed git blob (the standalone build is reproducible; my working copy was only autocrlf-inflated), copied the built shim to the wheel path, and exercised it in Node: it installs the transport (wraps `fetch`) with the proxy env set and throws without it. Fail-before by removing the shim (the new Python test fails); pass-after restored. - Observed result: `headroom/providers/opencode/hook-shim/handler.js` now ships in the package as a self-contained module, so a pip-installed `headroom wrap opencode` routes spawned Node children (npx MCPs, `tokensave serve`) through the proxy instead of leaving them unrouted, and never crashes them. - Not tested: a full pip-install-and-spawn on Linux with a live OpenCode session (no OpenCode client here). The shim is verified to load and wrap `fetch` under Node, the bundle is reproducible and byte-checked by CI, and the packaging path is maturin's standard file inclusion under `headroom/`. ## 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 did **not** edit `CHANGELOG.md`: it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Additional Notes The checkout keeps using `plugins/opencode/hook-shim/handler.js` (which imports `../dist/index.js` from the regular build), so dev behavior is unchanged; only the wheel gains the self-contained sibling. `entry.opencode.js` is byte-unchanged, so its existing CI guard still passes. The committed shim is stored with LF endings so the Linux CI byte-compare matches. |
||
|
|
9fd5ae3d53
|
chore: release main (#2679)
🤖 I have created a release *beep* *boop* --- <details><summary>0.34.0</summary> ## [0.34.0](https://github.com/headroomlabs-ai/headroom/compare/v0.33.0...v0.34.0) (2026-08-05) ### Features * **claude:** support Claude Code in VS Code ([#2752](https://github.com/headroomlabs-ai/headroom/issues/2752)) ([ |
||
|
|
3c10e8ff00
|
chore(docs): one documentation site, not two (#2784)
## Description The repo published **two** documentation sites from two source trees: ``` docs/ -> Next.js/Fumadocs -> headroom-docs.vercel.app <- canonical wiki/ -> MkDocs -> gh-pages branch -> github.io/headroom <- orphan ``` The Vercel site is what the README badge and **every** README deep link point at, and what `pyproject.toml` names as both `Homepage` and `Documentation`. The Pages site is referenced from **nowhere** in the repo — not README, not `pyproject`, not `CLAUDE.md`, not any docs page. I grepped for `github.io` and `gh-pages` across all of them and got zero hits. So it was costing work and causing breakage while nobody was reading it: - **Every documented change had to be written twice.** This session I wrote the same configuration content into `docs/content/docs/configuration.mdx` *and* `wiki/configuration.md`. That's the tax, and it compounds silently — the two drift and no one notices which is stale. - **It broke the Vercel deployment.** Each Pages deploy runs `mkdocs gh-deploy --force`, force-pushing `gh-pages`. Vercel's Git integration then tries to build that branch with Root Directory `docs`, which fails: *"The specified Root Directory `docs` does not exist"* — because `gh-pages` holds only the rendered site (`.nojekyll`, `404.html`, …). Timing was exact: ```text 23:06:44 main |
||
|
|
a9a2fbd74f
|
ci(docs): deploy Pages on wiki changes, drop the never-working Vercel job (#2746)
## Description
Two independent bugs in `.github/workflows/docs.yml`.
**1. Pages went stale because the push filter watched the wrong
directory.**
`mkdocs.yml` sets `docs_dir: wiki`, but the push filter listed `docs/**`
and not `wiki/**`. So a merge touching only `wiki/` never triggered the
workflow and the published Pages site silently went stale, while a
`docs/**`-only change triggered a Pages rebuild whose sources mkdocs
doesn't even read.
Push now filters on `wiki/**` + `mkdocs.yml`. **Pull requests keep
`docs/**`**, so `validate-nextjs` still catches a broken MDX change
before merge.
**2. `deploy-vercel` never worked — it wasn't a working path that went
stale.**
It ran `npx vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }}`,
but those secrets don't exist:
```text
$ gh api repos/headroomlabs-ai/headroom/actions/secrets --jq '.secrets[].name' | grep -i vercel
(nothing)
$ gh api orgs/headroomlabs-ai/actions/secrets --jq '.secrets[].name' | grep -i vercel
(nothing)
```
So it invoked the CLI with an empty `--token=` and exited 1, every time:
```text
30 failed runs on main, 2026-07-14 .. 2026-08-04
most recent:
30874173333
|
||
|
|
3f2ca99fe1
|
fix(ci): restrict Codecov shard uploads (#2745)
## Description Closes #2744 Restrict each Codecov Action v5 matrix upload to its declared `coverage-${{ matrix.shard }}.xml` report. This prevents automatic discovery from uploading the unsharded `coverage.xml` alongside every shard. ## Type of Change - [x] Bug fix (non-breaking change fixes an issue) ## Changes Made - Set Codecov Action `disable_search: true` for Python shard uploads. - Add a CI workflow contract test that protects the explicit-report-only setup. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check`) - [x] Type checking passes (`mypy headroom`) (not applicable: workflow/test-only change) - [x] New tests added new functionality - [x] Manual testing performed (not applicable: GitHub Actions will execute the workflow) ### Test Output ```text $ uv run --with ruff ruff format --check scripts/tests/test_ci_workflow.py 1 file already formatted $ uv run --with ruff ruff check scripts/tests/test_ci_workflow.py All checks passed! $ uv run --with pytest pytest scripts/tests/test_ci_workflow.py -q 2 passed ``` ## Real Behavior Proof - Environment: GitHub Actions Ubuntu runner using Python 3.12.13; Codecov Action v5. - Exact command / steps: Run the CI Python test matrix, which writes `coverage-${{ matrix.shard }}.xml`, then runs the Codecov Action upload step. Inspect the uploader's discovered/uploaded report list. - Observed result: Before this change, raw CI logs showed the Action explicitly uploading `coverage-2.xml` and additionally discovering/uploading `coverage.xml`. This PR configures `disable_search: true`; the workflow contract test confirms the explicit report setting and search disablement. Runtime upload evidence will be added from this draft PR's CI run. - Not tested: Codecov's final cross-shard patch calculation; that depends on Codecov processing the reports after CI completes. ## Review Readiness - [x] I have performed a self-review - [x] This PR ready for human review ## Checklist - [x] My code follows project's style guidelines - [x] I have performed a self-review - [x] I commented my code, particularly in hard-to-understand areas - [x] I made corresponding changes documentation (not applicable) - [x] My changes generate no new warnings - [x] I added tests prove my fix is effective or feature works - [x] New and existing unit tests pass locally changes - [x] I did **not** edit `CHANGELOG.md` — generated by release-please from Conventional Commit PR title (a CI guard enforces this) ## Additional Notes This is intentionally limited to the Codecov upload configuration and its workflow contract test. It does not include the unrelated Copilot Keychain fix. |
||
|
|
e0ce4b1d48
|
fix: remove rtk and lean-ctx CLI context tools (#2677)
## Description Removes both third-party CLI context tools — **rtk** and **lean-ctx** — and with them the context-tool selector itself. Headroom no longer downloads, installs or configures either one, and there is no replacement. The previous pass (#2344) gated only three entry points inside `headroom/cli/wrap.py`. That left the feature reachable in practice: | Gap | Effect | |---|---| | `scripts/install.sh:1544`, `install.ps1:1681` | Ran `rtk init --global --auto-patch` from bash/PowerShell, **bypassing the Python gate entirely** — `curl \| sh` still wrote a Claude Code `PreToolUse` hook regardless of `HEADROOM_RTK` | | `wrap.py` `_setup_context_tool_for_agent` | **`wrap openhands` was broken by default**: `rtk_required=True` met a gate returning `None` → `SystemExit(1)`. Invisible because all 8 openhands tests patched `_ensure_rtk_binary` to a fake path | | `proxy/helpers.py`, `subscription/tracker.py` | Proxy shelled out to `rtk gain` from `/stats`, the dashboard and `headroom perf`; the tracker polled it per contribution (`_RTK_WIRING_DEFAULT = "enabled"`) | | No cleanup path | Nothing removed artifacts an earlier default had installed, so a machine that once ran the old default kept rtk in the loop forever (#1669, #1955) | Also worth noting: the rtk binary download had **no SHA or signature verification** — only `rtk --version` as a smoke test. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [x] Code refactoring (no functional changes) ## Changes Made **Removed** — `headroom/rtk/` and `headroom/lean_ctx/` packages, `headroom/cli/wrap_rtk_metrics.py`, `_selected_context_tool` / `_setup_context_tool_for_agent` / `_VALID_CONTEXT_TOOLS`, the `--rtk` / `--no-rtk` / `--no-project-rtk` / `--keep-rtk` flags across all 18 wrap subcommands, `HEADROOM_RTK*`, the proxy-side `rtk gain` polling, the dashboard CLI-filtering panel (rows + all 8 `cliFiltering*` Alpine getters), `paths.rtk_path()` / `lean_ctx_path()`, the SDK path helpers, `benchmarks/rtk_loop_learn_eval.py`, and the `headroom/rtk/**` CI path filters. **Fails loudly, not silently** — `--context-tool` / `--no-context-tool` / `HEADROOM_CONTEXT_TOOL` are kept solely to error out. They live in shell profiles, aliases and CI jobs, and accepting them as a no-op would read as Headroom having quietly stopped working. The installers reject them too, which matters more than it looks: their arg parsers forward the first unknown flag **and everything after it** to the wrapped tool, so a leftover `--no-rtk` would have silently swallowed a following `--port` and then been ignored downstream. **New `headroom/context_tool_cleanup.py`** — deleting the code cannot help a machine that already ran the old default, since the hooks, binaries and injected guidance are durable on disk. `purge_context_tool_artifacts()` runs once per `wrap`/`unwrap` and removes the registered hook entries, the generated hook scripts, the Headroom-managed `~/.local/bin` symlinks, the vendored `~/.headroom/bin/{rtk,lean-ctx}` binaries, the `lean-ctx` MCP server entry and the marker-fenced instruction blocks. Deliberately conservative: idempotent, **skips** a malformed config rather than overwriting it, and only unlinks a symlink resolving inside Headroom's own bin dir so a user's own build is untouched. It reports on **stderr**, because `wrap/unwrap openclaw --prepare-only` emit machine-readable JSON on stdout as their entire contract. Skipped for `wrap selfheal` (runs from a SessionStart hook; must not race Claude Code's writer for `~/.claude.json`) and for `--help`, which must stay read-only. **Client-config hardening** (discovered while investigating a "corrupted Serena settings file" report) — `wrap.py` reset a settings file to `{}` when an existing file would not parse, then wrote that back. One hand-edited typo or a transient `EACCES`/`EINTR` on a valid file destroyed the user's `permissions`, `env` and `hooks`, on **every `headroom wrap claude`**. It now refuses to write. Separately, `fsutil.write_text` is now atomic (temp file + `fsync` + `os.replace`), fixing all 14 non-atomic client-config writes at once; it follows symlinks rather than replacing them (dotfile managers) and preserves an existing file's mode. **Deliberately kept** — `rtk` stays in the wrapper-peel list in `transforms/content_router.py`. It sits beside `sudo`/`env`/`timeout` as shell-command grammar, so `rtk cat f` is still classified as a file read for anyone running their own rtk install, which the purge intentionally leaves alone. ## 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 $ ruff check headroom/ tests/ e2e/ --exclude headroom/dashboard/templates All checks passed! $ ruff format --check headroom/ tests/ e2e/ --exclude headroom/dashboard/templates 1255 files already formatted $ mypy headroom/ Success: no issues found in 508 source files $ pytest tests/test_context_tool_cleanup.py -q 11 passed $ pytest tests/test_fsutil.py -q 12 passed $ pytest tests/test_cli/test_wrap_codex.py -q # 89 tests 89 passed in 431.68s $ pytest tests/test_cli/test_wrap_opencode.py -q 39 passed in 257.46s $ pytest tests/test_cli/test_wrap_helpers.py -q 45 passed $ pytest tests/test_paths.py -q 75 passed $ pytest tests/test_cli/test_unwrap_claude.py -q 14 passed $ pytest tests/test_proxy_savings_history.py -q 39 passed $ pytest tests/test_cli/test_wrap_copilot.py -q 27 passed $ pytest tests/test_cli/test_wrap_zcode.py -q 20 passed $ pytest tests/test_subscription_tracker.py -q 9 passed $ pytest tests/test_proxy_dashboard_stats_cache.py -q 5 passed, 1 skipped ``` Repo-wide grep for 14 removed symbols (`headroom.rtk`, `headroom.lean_ctx`, `_ensure_rtk_binary`, `_selected_context_tool`, `_get_context_tool_stats`, `rtk_path`, `lean_ctx_path`, `wrap_rtk_metrics`, `HEADROOM_RTK`, `cli_tokens_avoided`, `tokens_saved_rtk`, …) across `*.py`, `*.ts`, `*.sh`, `*.ps1`, `*.yml`, `*.html`: **zero hits**. Notable test changes: `test_wrap_openhands.py` no longer patches `_ensure_rtk_binary` and asserts `wrap openhands --prepare-only` exits 0 unpatched — the regression that was previously masked. `test_wrap_continue.py` and `test_wrap_hintfile_agents.py` were removed (every test drove RTK instruction injection). A new `test_subscription_tracker.py::test_load_state_written_before_cli_context_tools_were_removed` proves a pre-removal `subscription_state.json` still loads. ## Real Behavior Proof - **Environment:** macOS 15.4 (darwin 25.4.0), Python 3.12.6, Headroom @ this branch, real `~/.headroom` and `~/.claude` on the dev machine. - **Exact command / steps and observed result:** ```text # 1. Retired flag fails loudly instead of silently no-op'ing $ headroom wrap codex --prepare-only --context-tool rtk Error: CLI context tools (rtk, lean-ctx) have been removed from Headroom: they rewrote shell commands through a third-party binary Headroom no longer manages. Drop --context-tool / --no-context-tool and unset HEADROOM_CONTEXT_TOOL; `headroom wrap` uninstalls what they left behind on first run. $ HEADROOM_CONTEXT_TOOL=lean-ctx headroom wrap codex --prepare-only Error: CLI context tools (rtk, lean-ctx) have been removed from Headroom: ... # 2. install.sh rejects the retired flags (extracted parse_wrap_args harness) ['--no-rtk', '--port', '9999'] rc=1 ERROR: CLI context tools ... Drop --no-rtk ['--context-tool=rtk'] rc=1 ERROR: CLI context tools ... Drop --context-tool $ bash -n scripts/install.sh # syntax OK # 3. Purge ran against the real machine, which had all the orphaned artifacts $ python -c "from headroom.context_tool_cleanup import purge_context_tool_artifacts; ..." removed ~/.headroom/bin/lean-ctx (51 MB) removed ~/.headroom/bin/rtk (7.7 MB) removed ~/.local/bin/rtk (symlink into ~/.headroom/bin) removed ~/.claude/hooks/rtk-rewrite.sh removed 8 lean-ctx-* hook scripts # ~/.claude.json afterwards: 90 top-level keys, 19 projects, mcpServers unchanged # → ~59 MB reclaimed, no unrelated key touched # 4. stdout stays machine-readable while the purge reports (planted a fake artifact) $ headroom wrap openclaw --prepare-only --gateway-provider-id codex >out 2>err $ cat out {"enabled":true,"config":{"proxyPort":8787,...}} # parses as JSON $ cat err Retired CLI context tool cleanup: removed /Users/tcms/.headroom/bin/rtk # 5. --help is inert (planted artifact survives), a real run purges $ headroom wrap codex --help → artifact survived: CORRECT $ headroom wrap openclaw --prepare-only → purged: CORRECT # 6. MCP purge dry-run against a copy of the real 82 KB ~/.claude.json top-level keys 90 -> 90; projects 19 -> 19; LOST keys: none all content outside mcpServers byte-identical: True ``` Dashboard rendered via the Playwright test after the panel removal: "Token Savings" shows only `Proxy 0 (0.0%)` / `Of total wire: 36.86%`, and "Token Usage" reads Before Compression → Proxy Removed → After Compression with no "Filtered (this session)" row. Nothing below the removed panel broke. - **Not tested:** Windows and Linux (macOS only) — `install.ps1` is verified by brace-balance and inspection, not executed, since no `pwsh` is available locally. The wrap e2e suite (`e2e/wrap/run.py`) was updated but not run; it needs the Docker e2e image. `serena project index` interaction is exercised in the stacked base PR. ## 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 - [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 did **not** edit `CHANGELOG.md` — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Additional Notes **Stacked on #2676** (`tejas/serena-config-bootstrap`) — please merge that first; this PR's base should then be retargeted to `main`, or it will read as containing that fix too. **Breaking-change migration for users:** - Drop `--rtk`, `--no-rtk`, `--no-project-rtk`, `--keep-rtk`, `--context-tool`, `--no-context-tool` from any alias, script or CI job, and unset `HEADROOM_RTK*` / `HEADROOM_CONTEXT_TOOL`. They now error rather than being ignored, so the failure is immediate and self-explaining. - Previously-installed artifacts are purged automatically on the next `wrap`/`unwrap`; no manual cleanup needed. - `headroom perf --json` no longer carries a `cli_filtering` key, and `/stats` no longer returns a `context_tool` section. **Docs:** `docs/rtk-architecture.md` deleted; RTK/lean-ctx removed from `README.md`, `docs/content/docs/{configuration,opencode,grok-build,docker-install,filesystem-contract}.mdx`, `docs/observability.md` and the matching `wiki/` pages. `REALIGNMENT/09-phase-G-rtk-observability.md` is marked SUPERSEDED rather than deleted, to keep the planning record. **Follow-ups not in scope:** `_emit_wrap_interrupted` was deleted as dead code — its only caller was the `except KeyboardInterrupt` guarding the binary download, so with no download there is nothing slow left to interrupt. |
||
|
|
28aa53dc7c
|
chore: release main (#2339)
🤖 I have created a release *beep* *boop* --- <details><summary>0.33.0</summary> ## [0.33.0](https://github.com/headroomlabs-ai/headroom/compare/v0.32.0...v0.33.0) (2026-07-29) ### Features * **lossless:** factor shared directory prefix in the grep search fold ([#2547](https://github.com/headroomlabs-ai/headroom/issues/2547)) ([ |
||
|
|
5383c6bf2f
|
fix(release): sync generated version metadata on the release branch (#2659)
## Description
The 0.33.0 release PR (#2339) has sat in `changes-requested` since
2026-07-17. Root cause: **release-please only rewrites `pyproject.toml`
and its configured `extra-files`**, but other tracked files also carry
the version — and `server.json` is asserted byte-for-byte against
`render_server_json()`, which derives its version from `pyproject.toml`.
So the bump alone fails
`tests/test_mcp_registry/test_server_json.py::test_root_server_json_matches_builder`
(the `test (2)` shard) on every regenerated release PR.
Nothing in the repo regenerated `server.json` at all, so it fell behind
every release.
Unblocks #2339.
### Why the release *build* passes but the release PR does not
`release.yml` already runs `scripts/version-sync.py` immediately before
its own `verify-versions.py` gate (lines 145 and 278). That is why
`build` and `build-wheels` are green on #2339 despite the drift — it
syncs in the workspace, uncommitted. The regular CI test job does
**not** sync, so the fix has to be committed to the branch.
This also explains why reviewers kept seeing `verify-versions.py` fail
locally while CI's build jobs passed: the verifier is never run
un-synced inside `release.yml`.
## 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
- **`scripts/version-sync.py`**: also write `server.json`. It was the
one version-carrying file with no writer anywhere. Values are rewritten
in place so key order and formatting keep matching the builder's
byte-for-byte output (verified: the file is pure ASCII and round-trips
exactly through `json.dumps(..., indent=2) + "\n"`).
- **`.github/workflows/release-metadata-sync.yml`** (new): on a push to
`release-please--branches--**`, run version-sync → gate on
verify-versions → commit if changed.
- **Keyed off the branch push** because release-please force-regenerates
that branch on every merge to main. That is precisely what wiped the
hand-pushed metadata fixes on #2339 (`2a86c8ff`, `d5ea4dc5`) — a push
trigger re-heals after every regeneration instead of being lost.
- **Uses the same PAT as `release-please.yml`**: a `GITHUB_TOKEN` push
does not trigger workflows, so the release PR's checks would never
re-run against the synced commit and would stay red.
- **Idempotent**: the self-triggered rerun finds no diff and exits
before pushing, so the loop terminates after one no-op run.
- **Corrected pre-existing drift on `main`**: the agent-hooks plugin
manifests, both marketplace manifests, and `.releasemetadata` were
stranded at **0.31.0** — never bumped for 0.32.0 either.
`verify-versions.py` now passes on `main`.
### Why not more `extra-files` entries
That would need ~13 jsonpath entries restating what `version-sync.py`
already knows, and a jsonpath that fails to match **fails silently** —
the same class of failure this PR removes, discoverable only after a
real release PR regenerates. There is also no precedent for nested
jsonpath (`$.packages[0].version`, `$.metadata.version`) in the config
today; both existing entries are plain `$.version`. Running the script
keeps one source of truth, and files added to it later are covered with
no change here.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`) — no `headroom/` sources
touched
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ python -m pytest scripts/tests/ tests/test_release_workflows.py tests/test_mcp_registry/ -q
207 passed in 2.69s
$ ruff check scripts/version-sync.py scripts/tests/test_version_sync.py tests/test_release_workflows.py
All checks passed!
$ ruff format --check <same>
3 files already formatted
$ actionlint .github/workflows/release-metadata-sync.yml
(clean)
```
New tests:
- `test_server_json_version_is_synchronized` — version-sync moves both
`server.json` version fields and preserves the other keys.
- `test_release_metadata_sync_runs_on_release_please_branch` — asserts
the trigger, the sync→verify→commit ordering, the no-op guard, and the
PAT.
- `test_version_sync_covers_every_file_the_verifier_gates` — guards
`version-sync.py` and `verify-versions.py` against drifting apart again,
which is the root cause here.
## Real Behavior Proof
- **Environment:** macOS (Darwin arm64), Python 3.12, repo venv.
- **Exact command / steps:** reproduced the CI failure locally by
simulating release-please's partial bump, then applying the fix.
**Reproducing the exact `test (2)` failure** — set `pyproject` to 0.33.0
while `server.json` stays at 0.32.0, as release-please leaves it:
```text
$ python -m pytest tests/test_mcp_registry/test_server_json.py -q
FAILED tests/test_mcp_registry/test_server_json.py::test_root_server_json_matches_builder
1 failed, 3 passed
```
**After `version-sync.py`:**
```text
$ python scripts/version-sync.py && python -m pytest tests/test_mcp_registry/test_server_json.py -q
4 passed
```
**Both gates green on a simulated 0.33.0 bump:**
```text
$ python scripts/version-sync.py --version 0.33.0
Version synchronized to 0.33.0
$ python scripts/verify-versions.py
All versions aligned at 0.33.0
$ python -m pytest tests/test_mcp_registry/test_server_json.py -q
4 passed
```
**Idempotency** (the property the workflow's loop-termination relies
on): re-running against an already-synced tree leaves `pyproject.toml`,
`server.json`, `openclaw`, and `sdk/typescript` untouched.
- **Not tested:** the workflow has not executed on a real release-please
branch regeneration — that can only be exercised once this is on `main`
and release-please next updates #2339. The PAT push path and the
self-trigger no-op are reasoned from `release-please.yml`'s existing
token comment and from local idempotency, not observed in CI.
## 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 did **not** edit `CHANGELOG.md`
## Additional Notes
**Context on the v0.32.0 release failure, since it is easy to misread as
"images never build".** Every artifact built for v0.32.0 — all 5 wheel
platforms including Windows, all 16 Docker builds + 8 manifests +
`promote-latest`, npm, and GitHub Packages. Only `publish-pypi` failed
(PyPI attestations, already fixed by `
|
||
|
|
a30305bc4c
|
ci: require ONNX Runtime >= 1.24 and fail fast when it is missing or too old (#2591)
CI installed 'onnxruntime>=1.16.0' for the Rust ort runtime. That floor is 8 minor versions too low, and the failure mode below it is a silent hang. Why 1.24: ort-sys computes ORT_API_VERSION = 17 + one per enabled api-N feature, and Cargo features are additive across the graph. fastembed 5.17.3 enables api-24, so the constant resolves to 24 and ort rejects any lower runtime. Why it hangs instead of failing: on rejection ort calls Error::new() from inside load_dylib_from_path, which already runs inside the Once that setup_api() is initialising. Building the error re-enters that Once, and std::sync::Once blocks forever on re-entry. Reproduced in isolation with a bare Session::builder() and onnxruntime 1.21.1 - killed after >1h at 0% CPU, no output; ORT_DYLIB_PATH makes no difference. With 1.24.4 the same call returns in 1.2s and the kompress parity fixtures pass 21/21. Per @RubenAAA this is not limited to old runtimes: a box that resolves no libonnxruntime at all hangs identically (0.0% CPU, threads in futex_wait_queue, nothing onnx-shaped in /proc/<pid>/maps). Any failure inside load_dylib_from_path re-enters the Once, so a pin alone cannot close it. So this adds a pre-flight to the dylib step asserting, before any test runs, that onnxruntime imports, that its minor is >= 24, and that a libonnxruntime object exists under capi/. Each failure exits 1 with an ::error:: annotation naming the cause, instead of burning the 30-minute timeout with an empty log. Verified all three branches locally (absent -> rc=1, 1.21.1 -> rc=1, 1.24.4 -> rc=0) and in CI, where it resolved onnxruntime 1.28.0 and exported the .so path. pyproject.toml is deliberately untouched: bumping the floor there makes headroom-ai[all] unsatisfiable via a pillow chain (onnxruntime>=1.24 forces pillow>=10.3.0,<12.0 while [all] requires pillow>=12.3.0). The user-facing hazard via headroom/_ort.py remains open and needs its own change. |
||
|
|
e562d007d8
|
ci(rust): gate jobs with if: instead of a workflow-level paths filter (#2580)
Prerequisite for making `parity` a required status check on main. A workflow skipped by a top-level `paths:` filter never creates its check runs at all, so a required check sourced from it sits pending forever on any PR that misses those paths and the PR can never merge. A job skipped by `if:` still creates a check run, reports skipped, and GitHub counts skipped as success. Moves the seven path patterns verbatim from the `on:` block into a new `rust-changes` job (dorny/paths-filter) and gates all five existing jobs on `needs.rust-changes.outputs.rust == 'true'`. Named rust-changes, not changes, to avoid colliding with ci.yml's existing check. Job `name:` fields unchanged, so no check names move. schedule/workflow_dispatch force rust=true, preserving the nightly full-suite behaviour. CI spend unchanged: same jobs on the same PRs, plus a ~15s gate job on PRs that previously skipped the workflow outright. Verified by the PR itself — it edits rust.yml, which is in the path list, so it exercises the rust=true branch: rust-changes and parity both SUCCESS. Follow-up before adding parity to required checks: confirm a non-Rust PR reports parity as skipped rather than absent. ci.yml has the mirror-image problem (paths-ignore on docs) and is deliberately not addressed here. |
||
|
|
f54f04f5bf
|
feat(opencode): ship the transport plugin in pip installs (#2601)
## Description
The OpenCode transport plugin - the piece that gives `wrap opencode`
all-provider routing by tagging each request with `x-headroom-base-url`
- only exists in repo checkouts today. `headroom_opencode_plugin_path()`
resolves `plugins/opencode/dist/entry.opencode.js`, which pip wheels do
not ship, so every pip install silently degrades to the two-provider
(anthropic/openai) baseURL fallback. The function's own docstring
documents the gap ("a pip-only install that does not ship `plugins/`").
Shipping the existing build output is not enough: the regular tsup build
leaves `headroom-ai` and `@opencode-ai/plugin` as bare external imports,
which only resolve next to the checkout's `node_modules`. Copied into
site-packages, the file fails to load. This PR ships a self-contained
bundle inside the wheel instead.
Closes #
## 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
- `plugins/opencode/tsup.standalone.config.ts` + `npm run
build:standalone`: a second build of the loader entry with `noExternal:
[/.*/]` and `splitting: false` - a single self-contained file whose only
imports are node builtins.
- `headroom/providers/opencode/_dist/entry.opencode.js`: the committed
standalone bundle (452 KB). It sits inside the package directory, so
maturin's `python-source = "."` packaging picks it up into the wheel
with no build-system changes.
- `headroom_opencode_plugin_path()`: falls back to the packaged bundle.
Precedence otherwise unchanged: `HEADROOM_OPENCODE_PLUGIN_PATH` env
override, then a repo-checkout build (fresher during development), then
the packaged bundle.
- CI (`opencode-plugin.yml`): rebuilds the standalone bundle and fails
the run if the committed artifact drifted from source, with a one-line
fix instruction; workflow path triggers extended to
`headroom/providers/opencode/_dist/**`.
- `tests/test_providers_opencode_plugin_path.py`: packaged bundle exists
and is self-contained (no bare npm imports), env override wins, fallback
resolution order.
## 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 --frozen --extra dev pytest tests/test_providers_opencode_plugin_path.py \
tests/test_providers_opencode_config.py tests/test_providers_opencode_install.py
============================== 49 passed in 0.31s ==============================
$ uvx ruff check headroom/providers/opencode/runtime.py tests/test_providers_opencode_plugin_path.py
All checks passed!
$ uvx ruff format --check headroom/providers/opencode/runtime.py tests/test_providers_opencode_plugin_path.py
2 files already formatted
$ uv run --frozen --extra dev mypy headroom/providers/opencode/runtime.py
Success: no issues found in 1 source file
$ cd plugins/opencode && npm run build:standalone
ESM dist-standalone/entry.opencode.js 452.28 KB
ESM Build success in 28ms
```
## Real Behavior Proof
- Environment: macOS 15 (arm64), opencode 1.18.5 (Homebrew), node 22 /
npm 10, isolated `XDG_*` dirs so no real user config was touched.
- Exact command / steps:
1. `npm run build:standalone` in `plugins/opencode`.
2. Started a local header-logging HTTP listener on `127.0.0.1:9977`
(stands in for the proxy; logs method, path, headers, returns 401).
3. Registered the standalone bundle by absolute path in a scratch
`opencode.json` (`"plugin":
["<abs>/dist-standalone/entry.opencode.js"]`) with a `google` provider
entry and a fake API key. Note: the bundle's directory has **no**
`node_modules` - this is exactly the site-packages situation.
4. `HEADROOM_PROXY_URL=http://127.0.0.1:9977 opencode run -m
google/gemini-2.5-flash "say hi"`.
- Observed result: the listener received `POST
/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse` with
`User-Agent: opencode/1.18.5 ...` - i.e. the plugin loaded standalone
and rerouted a provider that the baseURL fallback cannot cover (native
Gemini wire format) to the proxy URL from `HEADROOM_PROXY_URL`.
- Not tested: Windows path resolution (pure `pathlib`, no platform
branches); wheel-build byte-determinism of the tsup output across OSes
(the CI drift check will surface it on the first divergent build).
## 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 did **not** edit `CHANGELOG.md` — it is generated by
release-please from my Conventional Commit PR title (a CI guard enforces
this)
## Screenshots (if applicable)
Not applicable - CLI/packaging change.
## Additional Notes
- Documentation checklist item: unchecked because the only doc surface I
found is the `headroom_opencode_plugin_path()` docstring, which this PR
rewrites to describe the three-step resolution order. Happy to add a
line to `docs/content/docs/` if there is a preferred page.
- A committed build artifact is not free: the CI drift check keeps it
honest, and the byte-compare relies on tsup/esbuild determinism under
`npm ci` (pinned lockfile). If you'd rather avoid the committed artifact
entirely, the alternative is publishing `headroom-opencode` to npm (its
`package.json` is publish-ready) and registering the plugin by package
name - happy to rework in that direction; the wheel-bundled path has the
advantage of version-locking the plugin to the backend it ships with.
- Downstream motivation: Headroom Desktop manages a long-lived shared
proxy (no `wrap` launcher) and wants to register this plugin from the
installed wheel path so OpenCode users get all-provider routing there
too.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
c15e557da1
|
ci(parity): make the parity harness a real per-PR gate (#2567)
Three hardening steps on the Rust-vs-Python parity harness: - Drop the dead maturin/venv step. headroom-parity has no pyo3 dependency, so the venv requirement, the `maturin develop` rebuild, and the CI job's Python toolchain were all overhead. Verified no-op: identical report, exit 0. - Register a text_crusher comparator. 6 recorded fixtures were invisible because the transform was missing from builtin_comparators(); parity-run only walks directories it has a comparator for. All 6 match on the first run. - Promote parity to a blocking per-PR gate. Safe to harden now because parity-run exits non-zero only on a Diff, so the 65 still-stubbed fixtures report Skipped and cannot turn it red. Harness: total=176 matched=111 skipped=65 diffed=0, exit 0. Deliberately not widening the path filter to Python paths: the fixtures are frozen recordings of Python output and the harness never invokes Python, so it measures Rust-vs-snapshot and a Python edit cannot move the result. |
||
|
|
f9cbdd6e39
|
fix(release): publish Windows wheel + sdist (disable PyPI attestations, #112) (#2405)
Automated by Headroom + Kimi (Fireworks) in a Modal Sandbox. Request: Make ONLY this one change, nothing else: in .github/workflows/release.yml, in the publish-pypi job's step that uses pypa/gh-action-pypi-publish, add exactly one line 'attestations: false' immediately after the 'skip-existing: true' line in its with: block (same indentation). Do NOT edit any other file, do NOT investigate the codebase, do NOT make any other change. Then finish. Co-authored-by: Headroom Kimi <kimi@headroom.dev> |
||
|
|
2bb14d1ab2
|
fix(ci): align Ruff tooling versions (#2406)
## Description Ruff currently has three independent versions: `uv.lock` resolves `0.14.14`, pre-commit runs `0.9.4`, and CI installs `0.15.17`. Contributors can therefore pass one formatter path and fail another. Make the exact Ruff pin in `pyproject.toml` the source of truth, align the lockfile and pre-commit hook to it, and make CI read that pin through a deterministic consistency verifier instead of carrying another hardcoded version. Closes #2398 ## 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 causes existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Pin the existing `[dev]` Ruff dependency to `0.15.17`, the formatter baseline already used by CI. - Refresh only Ruff in `uv.lock` with `uv 0.11.29`. - Align `ruff-pre-commit` to `v0.15.17`. - Add `scripts/verify-ruff-version.py` and run it from pre-commit and CI. - Make CI install the verified version read from `pyproject.toml` rather than a separate literal. ## Testing - [ ] Unit tests pass (`pytest`) — not run; no runtime source or test behavior changed. - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New deterministic guard proves the configuration fix - [x] Manual testing performed ### Test Output ```text # Before: run the verifier with the patched pyproject pin but base-branch # uv.lock, pre-commit config, and workflow. Ruff version mismatch detected: uv.lock uses Ruff 0.14.14, expected 0.15.17 .pre-commit-config.yaml uses Ruff 0.9.4, expected 0.15.17 ci.yml does not run 'python scripts/verify-ruff-version.py --print-version' ci.yml does not install Ruff from 'steps.ruff-version.outputs.version' $ python3 scripts/verify-ruff-version.py Ruff versions aligned at 0.15.17 $ uvx uv@0.11.29 lock --check Resolved 269 packages $ uvx uv@0.11.29 tree --locked --package ruff ruff v0.15.17 $ uvx ruff@0.15.17 check . All checks passed! $ uvx ruff@0.15.17 format --check . 1322 files already formatted $ uvx mypy@1.20.2 headroom --ignore-missing-imports Success: no issues found in 505 source files $ uvx --with tomli mypy@1.20.2 scripts/verify-ruff-version.py --ignore-missing-imports Success: no issues found in 1 source file $ uvx pre-commit run ruff --all-files Passed $ uvx pre-commit run ruff-format --all-files Passed $ uvx pre-commit run verify-ruff-version --all-files Passed ``` ## Real Behavior Proof - Environment: macOS 26.5, Python 3.14.6 locally; Python 3.10 fallback also exercised; `uv 0.11.29`, Ruff `0.15.17`, mypy `1.20.2`. - Exact command / steps: reproduced the mismatch using the base branch's real `uv.lock`, `.pre-commit-config.yaml`, and `.github/workflows/ci.yml`; then ran the verifier, locked Ruff tree, full Ruff check/format, mypy, and actual pre-commit hooks after the patch. - Observed result: the base state fails with all four drift points listed; the patched state reports one aligned Ruff version (`0.15.17`) and every formatter path passes. - Not tested: runtime proxy behavior and the pytest suite, because the change is limited to development-tool configuration, lock metadata, pre-commit, and CI wiring. ## Dependency / Supply-Chain Justification - Ruff is an existing development-only formatter maintained by Astral; this PR adds no new package. - `0.15.17` is required to fix local/CI reproducibility and has already been the repository's CI formatter baseline since #1295. - Install surface is limited to the `[dev]` extra, lint CI job, and pre-commit environment. Production/runtime dependencies are unchanged. - The `uv.lock` refresh updates only Ruff; no unrelated dependency upgrades are included. ## 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 the non-obvious consistency checks - [x] Documentation changes are N/A; contributor commands are unchanged - [x] My changes generate no new warnings - [x] The guard fails on the real base-state mismatch and passes after the fix - [ ] New and existing unit tests pass locally — not run; no runtime code changed - [x] I did not edit `CHANGELOG.md`; release-please will use the conventional PR title ## Additional Notes No formatter-driven source changes are included. AI assistance was used to inspect configuration, implement the verifier, and run validation. |
||
|
|
e7340aee65
|
ci(changelog): stop the CHANGELOG cascade — release-please owns it (#2329)
## Description Stops the recurring **CHANGELOG cascade** where several concurrent PRs all edit `CHANGELOG.md`'s `## Unreleased` section, so the first to merge turns the rest `DIRTY` — forcing a serial rebase-per-merge grind. Root cause: **release-please already generates `CHANGELOG.md`** from Conventional Commit titles (see `.release-please-config.json` `changelog-sections`), so the hand-written `## Unreleased` entries are *both redundant with release-please and the sole source of the conflicts*. The PR template and CONTRIBUTING were actively telling contributors to keep hand-editing it. `.gitattributes merge=union` (#2138) does not help — GitHub squash-merge ignores merge drivers. Fix: make release-please the only author of the changelog and stop hand-edits at the source. Closes # ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) — CI/process ## Changes Made - **`.github/workflows/changelog-guard.yml`** (new): fails any PR that modifies `CHANGELOG.md`, except release-please's own release PR (head branch `release-please--*`). Uses the preinstalled `gh` CLI — no third-party action to pin. - **`.github/PULL_REQUEST_TEMPLATE.md`**: flips the "I have updated the CHANGELOG.md" checkbox to "I did **not** edit CHANGELOG.md — release-please generates it from my Conventional Commit PR title." - **`CONTRIBUTING.md`**: replaces step 6 ("Update `CHANGELOG.md`") with the release-please policy. ## Testing - [x] `actionlint .github/workflows/changelog-guard.yml` — clean - [x] YAML parses (`yaml.safe_load`) - [x] Guard match logic unit-checked locally: fires on root `CHANGELOG.md`, ignores nested paths (`docs/CHANGELOG.md`) and PRs that don't touch it. ### Test Output ```text YAML OK actionlint OK root CHANGELOG.md -> FIRES nested docs/CHANGELOG.md -> pass no changelog -> pass ``` ## Real Behavior Proof - Environment: local clone + `actionlint`. - Exact steps: created the workflow, validated with actionlint + a YAML parse, and exercised the `grep -qx 'CHANGELOG.md'` decision against representative changed-file lists. - Observed result: guard fires only on a root-level `CHANGELOG.md` edit; the release-please branch is exempted via the job-level `if`. - Not tested: a live PR run in Actions (will exercise on this PR itself — note this PR does **not** touch `CHANGELOG.md`, so the guard should pass green here). ## Additional Notes - To *enforce* (block merge, not just show red), add **Changelog Guard / no-manual-changelog** to the branch's required status checks. - Follow-up: the ~10 currently-open PRs that hand-edit `CHANGELOG.md` will need that edit removed (a clean deletion now, not a conflict resolution) before they merge — after which the cascade is gone for good. |
||
|
|
46d4378cf7
|
feat(evals): weekly HotpotQA answer-recall report on the prose path (#1188)
## Description
Follow-up to **#1187** (the offline fidelity gate). That gate is
hermetic and **structured-only** (JSON tool outputs via Rust
compressors) so it can block every PR with zero setup. This PR adds the
genuinely-uncovered piece: **prose answer-recall on a real dataset
(HotpotQA)** in the **model-allowed weekly job**, where compression
routes through Kompress (ModernBERT).
> **Stacked on #1187.** Until that merges, this PR's diff shows its
commit too; it reduces to just `
|
||
|
|
5709291914
|
chore(release): harden local artifact smokes (#1824)
## Description
Harden the release artifact workflow so maintainers can reproduce npm
and Python release checks locally and so OpenClaw packaging does not
depend on a not-yet-published SDK version. This follow-up also keeps the
OpenClaw loader export contract explicit and updates the PR after the
branch was merged with current `headroomlabs/main`.
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [x] Documentation update
## Changes Made
- Added reusable local release smoke scripts for npm assets, Python
wheel/sdist artifacts, and the combined release gate.
- Switched the release workflow npm asset build to the reusable npm
builder.
- Made OpenClaw release asset building install against the just-built
local SDK tarball before rewriting packed metadata to the release
dependency range.
- Kept the OpenClaw source dependency registry-installable at
`headroom-ai@^0.22.3` while the packed artifact still ships
`^<release-version>`.
- Added `registerHeadroomPlugin` as a named export while preserving the
default `{ register }` OpenClaw loader contract.
- Added Windows development bootstrap docs/script and regression tests
for version sync, npm asset ordering, OpenClaw source installability,
and Python wheel smoke import isolation.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
uv run --with pytest python -m pytest tests/test_release_workflows.py scripts/tests/test_version_sync.py -q
44 passed, 1 warning
node --check scripts/build_npm_release_assets.mjs
node --check scripts/verify_npm_release_assets.mjs
python -m py_compile scripts/build_python_release_smoke.py scripts/release_smoke_all.py scripts/version-sync.py
python scripts/verify-versions.py
All versions aligned at 0.31.0
npm ci (plugins/openclaw)
added 88 packages, audited 89 packages, found 0 vulnerabilities
python scripts/release_smoke_all.py --out release-assets-local/all-pr1824-postmerge-fixed-20260710-104739
Verified npm release assets for 0.31.0
wheel metadata OK: headroom_ai-0.31.0-cp310-abi3-win_amd64.whl contains headroom/_core.pyd
sdist License-File metadata OK: ['LICENSE', 'NOTICE']
smoke-import OK: version=0.31.0 hello=headroom-core
```
## Real Behavior Proof
- Environment: Windows 11, Python 3.14.3, Node v24.14.0, npm 11.9.0, uv
0.11.15, Rust/Cargo already installed in the local development
environment.
- Exact command / steps: Ran `python scripts/release_smoke_all.py --out
release-assets-local/all-pr1824-postmerge-fixed-20260710-104739` after
syncing this branch with current `headroomlabs/main`.
- Observed result: The command built `headroom-ai-0.31.0.tgz`,
`headroom-openclaw-0.31.0.tgz`,
`headroom_ai-0.31.0-cp310-abi3-win_amd64.whl`, and
`headroom_ai-0.31.0.tar.gz`; npm verifier passed; the wheel installed
into a fresh venv and imported `headroom._core`.
- Not tested: Full GitHub Actions release matrix and publish jobs with
real PyPI/npm/GitHub Packages credentials.
## 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
- [x] 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
The post-merge full smoke initially failed because the OpenClaw source
package depended on `headroom-ai@^0.31.0`, which is not available on
public npm yet. The builder now installs OpenClaw against the just-built
local SDK tarball before build/pack, and then rewrites packed metadata
to `^0.31.0`.
---------
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
|
||
|
|
7ab83c5107
|
fix(router): stop protecting passing build/test output as error traces (#1740)
## Description  `content_has_strong_error_indicators()` (`headroom/transforms/error_detection.py`) protects any message/content-block from compression when it contains 2+ distinct indicator keywords (`error`, `fail`, `exception`, `traceback`, `fatal`, `panic`, `crash`). That heuristic false-positives on **passing** build/test tool output: `tsc`'s `"Found 0 errors"` plus a passing test run's `"0 failures"` trips both `error` and `fail` — 2 distinct hits — despite nothing failing. In a long JS/TS coding session this fired on nearly every request (confirmed against the `stats.json` attached to #1696), permanently protecting legitimate tool output from ever being compressed and explaining the reported 0.3% savings vs. the advertised 60-95%. Closes #1696 ## 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/transforms/error_detection.py`: strip common zero-result phrases before the 2-keyword scan in `content_has_strong_error_indicators()` — both `"N word"`/`"word N"` forms (`"0 errors"`, `"no failures"`) and `"label:value"`/`"label=value"` forms (`"Failures: 0"`, `"errors=0"`), covering `error(s)` and `fail`/`failed`/`failing`/`failure(s)` (the scan matches `fail` by substring, so all inflections needed covering). - `tests/test_error_detection.py` (new file — no prior coverage existed): 7 tests covering real error/traceback detection, single-keyword safety, `tsc`/`eslint` passing summaries, the `"0 failed"` regression a reviewer caught, label:value formats, and that a genuine second indicator elsewhere in the same blob still triggers protection. - `.github/pr-images/issue-1696-error-protection-fix.svg`: diagram explaining the mechanism (embedded above). ## Testing - [x] Unit tests pass (`pytest`) - [ ] Linting passes (`ruff check .`) — not run locally, CI `lint` check is green - [ ] Type checking passes (`mypy headroom`) — not run locally, CI is green - [x] New tests added for new functionality - [x] Manual testing performed (see Real Behavior Proof) ### Test Output ```text $ .venv/Scripts/python -m pytest tests/test_error_detection.py tests/test_transforms/test_content_router.py tests/test_transforms_content_router.py -q tests\test_error_detection.py ....... [ 7%] tests\test_transforms\test_content_router.py ........................... [ 34%] ........................... [ 62%] tests\test_transforms_content_router.py ................................ [ 94%] ..... [100%] ============================= 98 passed in 1.21s ============================== ``` ## Real Behavior Proof - Environment: Windows 11, Python 3.14.5, local venv, `headroom._core` built via `maturin develop --release` (not prebuilt in a fresh checkout) - Exact command / steps: ran the reporter's exact scenario patterns (`"Found 0 errors\nTests: 0 failures, 42 passed"`, eslint's `"0 problems (0 errors, 0 warnings)"`) through `content_has_strong_error_indicators()` directly, before and after the fix - Observed result: before → `True` (wrongly protected); after → `False` (correctly compressible). Real failure text (`Traceback... fatal error`) still returns `True` after the fix. - Not tested: have not reproduced the full KiloCode/proxy session end-to-end locally (no access to the reporter's actual traffic) — root cause was confirmed via the `stats.json` they attached to the issue, which shows `router:protected:error_output` firing on nearly every request in their session. ## 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 — internal heuristic, no user-facing docs reference it) - [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 (release-please generates this automatically from commit messages) ## Screenshots (if applicable) See the diagram embedded in Description above. ## Additional Notes **Investigation trail**: ruled out `protect_recent_reads_fraction` (proxy already overrides its `0.0` dataclass default to `0.3` in token mode, the default) before confirming the error-protection false-positive via the reporter's `stats.json`. **Response to review comments** (@AbelVM, @sparkbugz): prose mentions like `"Fix the errors in the code."` or `"console.error(...)"` contain only 1 distinct indicator keyword and were already safe under the pre-existing 2-keyword threshold — not something this PR changes. The broader concern about other CI summary formats is addressed above (label:value forms). A case like genuine prose that happens to mention *two* distinct keywords together (e.g. "there are errors and it failed") is a known limitation of a keyword-substring heuristic in general, predates this PR, and is out of scope here — downstream compressors (LogCompressor) still preserve real error lines even when a block isn't gate-protected, so the failure mode there is "slightly stricter than ideal," not data loss. **Response to @JerrettDavis's CHANGES_REQUESTED**: fixed in the follow-up commit — `"0 failed"` is now stripped (previously only `failing`/`failure(s)` were), with a regression test for the exact reproduction given. |
||
|
|
c46cd8f950
|
fix(core): load ONNX Runtime dynamically so headroom._core imports on non-AVX2 x86-64 (#1715)
## Description
`import headroom._core` dies with SIGILL (`Illegal instruction`) on
x86-64 CPUs without AVX2 (Pentium N4200, Celeron N4500, AMD FX 8350 —
all reported on the issue). The repo sets no `RUSTFLAGS`/`target-cpu`
anywhere, so first-party Rust code is baseline x86-64; the AVX2 code
comes from Microsoft's prebuilt ONNX Runtime, statically linked into the
extension by fastembed's `ort-download-binaries-rustls-tls` feature on
non-Windows targets. Because it is statically linked, its code is mapped
and initialized when the extension module loads — **before** the runtime
AVX2 guard from #1162 can run, which is why that fix helped Magika init
but not the import-time crash.
Fix, mirroring what Windows already does for its own reasons (DirectML
link libs): build with `ort-load-dynamic` on every platform, so ONNX
Runtime is only `dlopen`'d at first use, where the #1162 AVX2 guard
falls back to the non-ONNX detection tiers on unsupported CPUs. Since
both target blocks became identical, they are collapsed into one
platform-independent `fastembed` dependency.
To keep Magika/fastembed working out of the box on Linux/macOS, the
existing `ORT_DYLIB_PATH` auto-pin (`headroom/_ort.py`, previously
Windows-only) now resolves the pip `onnxruntime` package's shared
library on all platforms (`onnxruntime.dll` / `libonnxruntime.so*` /
`libonnxruntime*.dylib`). The pip `onnxruntime` CPU wheels use runtime
CPU dispatch, so they also work on pre-AVX2 machines — non-AVX2 users
get working ML detection instead of a crash. Without the `onnxruntime`
package, ML detection degrades gracefully to the non-ONNX tiers exactly
as it already does on Windows.
Fixes #1278
## 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)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- `crates/headroom-core/Cargo.toml`: replaced the per-target `fastembed`
blocks (`ort-download-binaries-rustls-tls` on non-Windows,
`ort-load-dynamic` on Windows) with a single platform-independent
dependency on `ort-load-dynamic`, with a comment documenting both the
DirectML and the AVX2/#1278 rationale.
- `Cargo.lock`: regenerated — `ort-sys` drops its static-download
dependencies (`hmac-sha256`, `lzma-rust2`, `ureq`); no version bumps.
- `headroom/_ort.py`: `ORT_DYLIB_PATH` auto-pin extended from
Windows-only to all platforms via a small `_find_dylib` helper that
resolves the platform's shared-library name inside the pip `onnxruntime`
package.
- `tests/test_transforms/test_ort_dylib.py`: replaced the obsolete
`test_noop_on_non_windows` with Linux (versioned `.so`) and macOS
(`.dylib`) pin tests; module docstring updated.
- `docs/content/docs/configuration.mdx`: `ORT_DYLIB_PATH` row updated
from Windows-only wording to the cross-platform behavior.
## 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
$ cargo fmt --all -- --check && cargo clippy --workspace -- -D warnings && cargo test -p headroom-core --lib
clean
test result: 844 passed; 0 failed; 1 ignored
$ python -m pytest tests/test_transforms/test_ort_dylib.py -q
8 passed
$ ruff check headroom/_ort.py tests/test_transforms/test_ort_dylib.py
All checks passed!
```
## Real Behavior Proof
- Environment: Windows 11 (AVX2-capable — the SIGILL itself is not
reproducible on this machine), Python 3.13, Rust 1.95.0, local checkout
branched from `upstream/main` (
|
||
|
|
b0fa84e84d
|
fix: add Vercel deploy config and workflow for docs site (#1739)
## Description The Vercel docs site at headroom-docs.vercel.app had no automated deployment pipeline, so newly added pages (persistent-installs, savings) return 404 despite existing in the repo and building correctly locally. Closes #1730 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking function added) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Add docs/vercel.json with explicit Next.js project config (framework, build/install commands) - Add deploy-vercel job to .github/workflows/docs.yml to auto-deploy on pushes to main touching docs/** ## Testing - [x] Unit tests pass (pytest) - [ ] Linting passes (ruff check .) - [ ] Type checking passes (mypy headroom) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ``` Local build verification: cd docs && npm ci && npm run build Build succeeded - persistent-installs and savings pages generated at .next/server/app/docs/persistent-installs.html and .next/server/app/docs/savings.html ``` ## Real Behavior Proof - Environment: Linux x86_64, Node.js 20 - Exact command / steps: 1. cd docs && npm ci && npm run build 2. Checked .next/server/app/docs/ for generated HTML artifacts 3. Verified source.getPage(["persistent-installs"]) returns page object - Observed result: Both pages build and render correctly locally - Not tested: Live Vercel deployment requires maintainer secrets (VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID) ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Additional Notes Requires three repo secrets: VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID. |
||
|
|
fce93bf39a
|
fix(ci/deps): clear audit and release smoke failures (#2190)
## Summary - add a uv constraint floor for `setuptools>=83.0.0` to address `PYSEC-2026-3447` - refresh `uv.lock` so the production audit export resolves with `setuptools 83.0.0` - harden the Release wheel smoke-import gate by retrying Ubuntu-container `apt-get` operations and using `--fix-missing` - keep the generated `requirements-prod.txt` uncommitted; it is produced by the security workflow ## Why This clears the new dependency audit alert that made PRs red: - `setuptools 80.10.2` - `PYSEC-2026-3447` - fixed in `83.0.0` While validating the queue, the same PR class also hit a Release smoke-import failure in the Ubuntu 22.04 ARM container due apt mirror skew: `E: Failed to fetch ... python3-httplib2_0.20.2-2ubuntu0.1_all.deb 404 Not Found` The smoke gate should still fail for broken wheels, but transient apt mirror skew should not make unrelated PRs red. ## Lockfile impact - `setuptools 80.10.2 -> 83.0.0` - `torch 2.12.1 -> 2.13.0`, required for pip resolver compatibility with `setuptools 83.0.0` in the exported audit set - `cuda-toolkit 13.0.2 -> 13.0.3.0`, pulled by the torch lock refresh - uv also refreshed the existing project metadata for the sandbox extra so `uv lock --check` passes ## Validation - `uv lock --check` - `uv export --frozen --no-dev --no-emit-project --no-hashes --extra all --format requirements-txt > requirements-prod.txt` - confirmed generated `requirements-prod.txt` contains `setuptools==83.0.0`, `torch==2.13.0`, `cuda-toolkit==13.0.3.0` - `uvx pip-audit -r requirements-prod.txt` -> No known vulnerabilities found - `python -m pytest tests/test_release_workflows.py -q` -> 32 passed - `uvx ruff@0.15.17 check tests/test_release_workflows.py` -> All checks passed - `git diff --check` |
||
|
|
58d3445d23
|
ci: scope native/wheel/dashboard jobs to relevant paths (unstarve the queue) (#2155)
## Problem CI's `changes` paths-filter is too coarse: both `code` and `e2e` include the catch-all `headroom/**`, so **any** Python change fires the full matrix — including the scarce-runner hogs: `build-wheel-windows`, `macos-native-wrapper`, `windows-native-wrapper`, `docker-native-e2e`. macOS runners cap at 5 on the Free plan, so a trivial Python PR burns the scarcest resource. `init-e2e`/`wrap-e2e` trigger on `headroom/**` too. ## Change (CI config only — no product code) - **ci.yml:** split `changes` into `code` / `native` / `dashboard` / `packaging`. Re-gate: | job | before | after | |-----|--------|-------| | `build-wheel-windows` | code | `packaging` | | `test-dashboard-ui` | code | `dashboard` | | `docker-native-e2e` | e2e | `native` | | `windows-native-wrapper` | e2e | `native` | | `macos-native-wrapper` | e2e | `native` | - **init-e2e / wrap-e2e:** scoped off `headroom/**` onto the `cli`/`install`/`providers`/`rtk` subpaths the flows exercise (mirrors the existing `*-native-e2e` workflows). ## Safety - Verified no job `needs:` any of the re-gated jobs, so no dependency chain breaks. `test-dashboard-ui` still gets `build-wheel` (dashboard files are under `headroom/` ⟹ `code` too). - Job-level `if:` skips resolve as **neutral** (not failing), so this is safe even if branch protection is added. - Full matrix still runs on any `.github/workflows/**` change and on push to `main` (post-merge safety net). ## Net A pure-Python logic PR skips ~7-9 jobs incl. all 3 native platform builds — freeing the macOS/Windows caps that were starving the queue. |
||
|
|
6efd01f707
|
ci: concurrency-cancel Docker + Merge Conflicts to stop merge-spree pileups (#2138)
## Description
`docker.yml` builds a full multi-arch image on **every push to `main`**
with **no concurrency group**, so a merge spree stacks one build per
commit. Against the Free-plan **20 concurrent-job cap**, those long
builds hog the runner pool and starve the `CI` (`test`/`lint`/`build`)
jobs that PR merges actually depend on. This adds concurrency-cancel so
only the latest build per ref runs — `cancel-in-progress` scoped to
`main` so a release tag's publish (its own ref) is never killed. Same
fix for the per-PR **Merge Conflicts** check.
## Type of Change
- [x] Repo infrastructure / CI (no functional change)
## Changes Made
- `.github/workflows/docker.yml`: `concurrency: docker-${{ github.ref
}}`, cancel-in-progress on `main` only.
- `.github/workflows/merge-conflicts.yml`: per-PR concurrency-cancel.
## Testing
- [x] YAML validated (`yaml.safe_load` on both) — release/tag builds
unaffected (separate ref group).
## Real Behavior Proof
- Before: 24 merges → 24 stacked Docker builds queued against 20 slots.
- After: only the latest `main` Docker build runs; older superseded ones
auto-cancel; release publishes untouched.
|
||
|
|
2c9eb7c5f1
|
feat(simulators): add provider simulator service (#2014)
## Description
Adds a Rust-only `headroom-simulators` workspace crate: a deterministic
local upstream simulator service for Headroom proxy and pipeline
validation. It supplies configurable stubs plus bottled provider-shaped
responses for supported provider/path surfaces without calling real
LLMs.
## Type of Change
- [x] 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)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- Added `crates/headroom-simulators` Rust crate with library and
`headroom-simulators` binary.
- Added clean domain classification for supported surfaces: Anthropic
`/v1/messages`, OpenAI chat/responses/conversations, Bedrock
invoke/stream routes, Vertex raw/stream predict, health, and generic
fallback.
- Added JSON-configured stub matching by method, path, body substring,
and JSON pointer.
- Added bottled provider-shaped JSON, SSE, and Bedrock EventStream
responses for unconfigured requests.
- Added a container `Dockerfile` and README for local/GitHub Actions
usage.
- Added unit and HTTP integration tests for defaults, configured stubs,
SSE, Vertex, and Bedrock EventStream behavior.
- Added proxy-level simulator-backed E2E tests that run Headroom against
the simulator across Anthropic, OpenAI Chat, OpenAI Responses, OpenAI
Conversations, Bedrock invoke/converse/streaming, Vertex raw/stream
predict, and upstream health.
- Added simulator-backed provider error-path E2E coverage for OpenAI
429, Anthropic 529, Bedrock 502, and Vertex 503 responses flowing
through Headroom unchanged.
- Added Headroom-owned preflight error E2E coverage proving Bedrock
missing credentials and invalid Vertex envelopes stop inside the proxy
instead of silently falling through to the simulator/provider.
- Fixed direct Rust `headroom-core` binaries/tests on Windows so Magika
initializes ONNX Runtime via `ort::init_from` from an explicit pip
`onnxruntime` library path, with fail-fast fallback only when no safe
runtime is discoverable.
- Added a Rust CI `simulator-e2e` matrix for `ubuntu-latest`,
`macos-latest`, and `windows-latest` that runs `cargo test -p
headroom-proxy --test e2e_simulators`.
- Gated dynamic Magika `Path`/`PathBuf` imports to Windows and x86_64
macOS so Linux clippy does not see unused dynamic-ORT-only imports.
## Testing
- [ ] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
cargo fmt --all -- --check
# passed
cargo clippy --workspace -- -D warnings
# passed
$env:ORT_DYLIB_PATH=$null
cargo test -p headroom-core transforms::magika_detector::tests:: --lib
# 17 passed, 0 failed; Magika initialized from discovered pip
onnxruntime DLL
$env:ORT_DYLIB_PATH=$null
cargo test --workspace
# passed
gitleaks protect --staged --no-banner --redact
# no leaks found
gitleaks git --log-opts="headroomlabs/main..HEAD" --no-banner --redact
# 5 commits scanned; no leaks found
## Real Behavior Proof
- **Environment:** Windows PowerShell, Rust toolchain `1.95.0`, clean
worktree from `headroomlabs/main` at `
|
||
|
|
48f06caca7
|
ci: add Windows wheel build job (win_amd64) (#1086)
### Summary
Adds `build-wheel-windows` job to the CI pipeline that compiles the Rust
extension on `windows-latest` and uploads the resulting `.whl` as a
separate artifact (`headroom-wheel-windows`).
This addresses the long-standing missing Windows wheel.
### Changes
- New job `build-wheel-windows`: mirrors the existing `build-wheel`
(Linux) job
- Uses `dtolnay/rust-toolchain@stable` for Rust setup on Windows
- Uses `Swatinem/rust-cache` for dependency caching
- Builds with CI cargo profile for speed
- 45-minute timeout (Windows Rust builds are slower)
- Uploads wheel as `headroom-wheel-windows` artifact
### Testing
✅ **Local compilation verified**: built v0.26.0 from source on Windows
10 (Python 3.12.10, Rust 1.96.0, MSVC Build Tools 2022). The wheel
installed and ran successfully.
### Notes
Only the CI-profile build is added here. The release-wheel publish
(`release.yml`) can be updated in a follow-up PR once this basic Windows
build is proven in CI.
Co-authored-by: Win He <win-he@users.noreply.github.com>
|
||
|
|
98ff203f98
|
ci: allow PyPI deps during CPU torch install (#1930)
## Description Fixes the CI failure exposed on the ` |
||
|
|
2d418335a1 | ci: preserve merge labels while state is unknown | ||
|
|
595b709a5b | ci: keep ready label off changes-requested PRs | ||
|
|
49a1a1405b
|
ci(opencode): compile + test the OpenCode plugin on changes (#1750)
The OpenCode plugin (`plugins/opencode`) is the routing shim that carries `headroom wrap opencode` traffic through the proxy — but **no CI job ever compiled it**. It has `typecheck`/`build`/`test` scripts that only ran locally, so: - TypeScript 6.x (#1687) and @types/node 26 (#1688) major-bump PRs had **zero build evidence** (why they're held). - Any source edit to the plugin could silently break the build. This adds a path-gated workflow that runs `npm ci → typecheck → build → test` whenever `plugins/opencode/**` (or this workflow) changes. Matches repo conventions (`setup-node@v6`, node 20, npm cache). **Verified green locally on main:** `tsc --noEmit` clean, `tsup` build ok, 13 vitest tests pass. Unblocks safe evaluation of the held dependency-bump PRs and protects the routing plugin going forward. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
662b7bc00e
|
fix(release): sync all package versions to v0.31.0 (#1882)
## Description Current `main` has advanced the core package versions to `0.31.0`, but the plugin marketplace manifests and hook plugin manifests were still left at `0.30.0`. This PR now keeps the original version-sync intent while updating the remaining metadata to the current release line. It also preserves the previously-added `lxml-html-clean>=0.4.5` security floor in `pyproject.toml` / `uv.lock` so the security audit remains unblocked. Closes #1872 ## 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 - Synced `pyproject.toml`, SDK/package manifests, plugin manifests, marketplaces, `.release-please-manifest.json`, and editable package lock metadata to `0.31.0`. - Updated the OpenClaw plugin dependency on `headroom-ai` to `^0.31.0`. - Merged current `main` and resolved the version metadata conflicts in favor of current `0.31.0` alignment. ## Testing - [x] Unit tests pass (`pytest tests/test_plugin_manifests.py scripts/tests/test_version_sync.py -q`) - [ ] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [ ] New tests added for new functionality - [x] Manual testing performed (`python scripts/verify-versions.py`) ### Test Output ```text python scripts/verify-versions.py All versions aligned at 0.31.0 pytest tests/test_plugin_manifests.py scripts/tests/test_version_sync.py -q 10 passed in 0.42s ``` ## Real Behavior Proof - Environment: Windows, Python 3.13.13, PR head after merging current `main`. - Exact command / steps: Ran `python scripts/verify-versions.py` and `pytest tests/test_plugin_manifests.py scripts/tests/test_version_sync.py -q`. - Observed result: Version verification exits successfully with `All versions aligned at 0.31.0`; focused manifest/version-sync tests pass. - Not tested: Full wheel/build matrix; this is metadata-only version alignment and CI will cover the broader matrix. ## 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] My changes generate no new warnings - [x] New and existing focused tests pass locally with my changes - [ ] I have made corresponding changes to the documentation - [ ] I have updated the CHANGELOG.md if applicable --------- Co-authored-by: JerrettDavis <mxjerrett@gmail.com> |
||
|
|
ebd23152d5
|
ci: bump actions/cache from 5 to 6 (#1413)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/actions/cache/releases">actions/cache's releases</a>.</em></p> <blockquote> <h2>v6.0.0</h2> <h2>What's Changed</h2> <ul> <li>Update packages, migrate to ESM by <a href="https://github.com/Samirat"><code>@Samirat</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1760">actions/cache#1760</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v5...v6.0.0">https://github.com/actions/cache/compare/v5...v6.0.0</a></p> <h2>v5.1.0</h2> <h2>What's Changed</h2> <ul> <li>Bump <code>@actions/cache</code> to v5.1.0 - handle read-only cache access by <a href="https://github.com/jasongin"><code>@jasongin</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1775">actions/cache#1775</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v5...v5.1.0">https://github.com/actions/cache/compare/v5...v5.1.0</a></p> <h2>v5.0.5</h2> <h2>What's Changed</h2> <ul> <li>Update ts-http-runtime dependency by <a href="https://github.com/yacaovsnc"><code>@yacaovsnc</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1747">actions/cache#1747</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v5...v5.0.5">https://github.com/actions/cache/compare/v5...v5.0.5</a></p> <h2>v5.0.4</h2> <h2>What's Changed</h2> <ul> <li>Add release instructions and update maintainer docs by <a href="https://github.com/Link"><code>@Link</code></a>- in <a href="https://redirect.github.com/actions/cache/pull/1696">actions/cache#1696</a></li> <li>Potential fix for code scanning alert no. 52: Workflow does not contain permissions by <a href="https://github.com/Link"><code>@Link</code></a>- in <a href="https://redirect.github.com/actions/cache/pull/1697">actions/cache#1697</a></li> <li>Fix workflow permissions and cleanup workflow names / formatting by <a href="https://github.com/Link"><code>@Link</code></a>- in <a href="https://redirect.github.com/actions/cache/pull/1699">actions/cache#1699</a></li> <li>docs: Update examples to use the latest version by <a href="https://github.com/XZTDean"><code>@XZTDean</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li> <li>Fix proxy integration tests by <a href="https://github.com/Link"><code>@Link</code></a>- in <a href="https://redirect.github.com/actions/cache/pull/1701">actions/cache#1701</a></li> <li>Fix cache key in examples.md for bun.lock by <a href="https://github.com/RyPeck"><code>@RyPeck</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li> <li>Update dependencies & patch security vulnerabilities by <a href="https://github.com/Link"><code>@Link</code></a>- in <a href="https://redirect.github.com/actions/cache/pull/1738">actions/cache#1738</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/XZTDean"><code>@XZTDean</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li> <li><a href="https://github.com/RyPeck"><code>@RyPeck</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v5...v5.0.4">https://github.com/actions/cache/compare/v5...v5.0.4</a></p> <h2>v5.0.3</h2> <h2>What's Changed</h2> <ul> <li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li> <li>Bump <code>@actions/core</code> to v2.0.3</li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v5...v5.0.3">https://github.com/actions/cache/compare/v5...v5.0.3</a></p> <h2>v.5.0.2</h2> <h1>v5.0.2</h1> <h2>What's Changed</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's changelog</a>.</em></p> <blockquote> <h1>Releases</h1> <h2>How to prepare a release</h2> <blockquote> <p>[!NOTE] Relevant for maintainers with write access only.</p> </blockquote> <ol> <li>Switch to a new branch from <code>main</code>.</li> <li>Run <code>npm test</code> to ensure all tests are passing.</li> <li>Update the version in <a href="https://github.com/actions/cache/blob/main/package.json"><code>https://github.com/actions/cache/blob/main/package.json</code></a>.</li> <li>Run <code>npm run build</code> to update the compiled files.</li> <li>Update this <a href="https://github.com/actions/cache/blob/main/RELEASES.md"><code>https://github.com/actions/cache/blob/main/RELEASES.md</code></a> with the new version and changes in the <code>## Changelog</code> section.</li> <li>Run <code>licensed cache</code> to update the license report.</li> <li>Run <code>licensed status</code> and resolve any warnings by updating the <a href="https://github.com/actions/cache/blob/main/.licensed.yml"><code>https://github.com/actions/cache/blob/main/.licensed.yml</code></a> file with the exceptions.</li> <li>Commit your changes and push your branch upstream.</li> <li>Open a pull request against <code>main</code> and get it reviewed and merged.</li> <li>Draft a new release <a href="https://github.com/actions/cache/releases">https://github.com/actions/cache/releases</a> use the same version number used in <code>package.json</code> <ol> <li>Create a new tag with the version number.</li> <li>Auto generate release notes and update them to match the changes you made in <code>RELEASES.md</code>.</li> <li>Toggle the set as the latest release option.</li> <li>Publish the release.</li> </ol> </li> <li>Navigate to <a href="https://github.com/actions/cache/actions/workflows/release-new-action-version.yml">https://github.com/actions/cache/actions/workflows/release-new-action-version.yml</a> <ol> <li>There should be a workflow run queued with the same version number.</li> <li>Approve the run to publish the new version and update the major tags for this action.</li> </ol> </li> </ol> <h2>Changelog</h2> <h3>6.1.0</h3> <ul> <li>Bump <code>@actions/cache</code> to v6.1.0 to pick up <a href="https://redirect.github.com/actions/toolkit/pull/2435">actions/toolkit#2435 Handle cache write error due to read-only token</a></li> <li>Switch redundant "Cache save failed" warning to debug log in save-only</li> </ul> <h3>6.0.0</h3> <ul> <li>Updated <code>@actions/cache</code> to ^6.0.1, <code>@actions/core</code> to ^3.0.1, <code>@actions/exec</code> to ^3.0.0, <code>@actions/io</code> to ^3.0.2</li> <li>Migrated to ESM module system</li> <li>Upgraded Jest to v30 and test infrastructure to be ESM compatible</li> </ul> <h3>5.0.4</h3> <ul> <li>Bump <code>minimatch</code> to v3.1.5 (fixes ReDoS via globstar patterns)</li> <li>Bump <code>undici</code> to v6.24.1 (WebSocket decompression bomb protection, header validation fixes)</li> <li>Bump <code>fast-xml-parser</code> to v5.5.6</li> </ul> <h3>5.0.3</h3> <ul> <li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li> <li>Bump <code>@actions/core</code> to v2.0.3</li> </ul> <h3>5.0.2</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href=" |
||
|
|
bfe4b4d21f
|
ci: bump actions/checkout from 4 to 7 (#1414)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p> <blockquote> <h2>v7.0.0</h2> <h2>What's Changed</h2> <ul> <li>block checking out fork pr for pull_request_target and workflow_run by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li> <li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li> <li>Bump flatted from 3.3.1 to 3.4.2 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li> <li>Bump js-yaml from 4.1.0 to 4.2.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li> <li>Bump <code>@actions/core</code> and <code>@actions/tool-cache</code> and Remove uuid by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li> <li>upgrade module to esm and update dependencies by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li> <li>Bump the minor-npm-dependencies group across 1 directory with 3 updates by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li> <li>getting ready for checkout v7 release by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2464">actions/checkout#2464</a></li> <li>update error wording by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2467">actions/checkout#2467</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6.0.3...v7.0.0">https://github.com/actions/checkout/compare/v6.0.3...v7.0.0</a></p> <h2>v6.0.3</h2> <h2>What's Changed</h2> <ul> <li>Update changelog by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li> <li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> <li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li> <li>Update changelog for v6.0.3 by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/yaananth"><code>@yaananth</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p> <h2>v6.0.2</h2> <h2>What's Changed</h2> <ul> <li>Add orchestration_id to git user-agent when ACTIONS_ORCHESTRATION_ID is set by <a href="https://github.com/TingluoHuang"><code>@TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2355">actions/checkout#2355</a></li> <li>Fix tag handling: preserve annotations and explicit fetch-tags by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6.0.1...v6.0.2">https://github.com/actions/checkout/compare/v6.0.1...v6.0.2</a></p> <h2>v6.0.1</h2> <h2>What's Changed</h2> <ul> <li>Update all references from v5 and v4 to v6 by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2314">actions/checkout#2314</a></li> <li>Add worktree support for persist-credentials includeIf by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li> <li>Clarify v6 README by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2328">actions/checkout#2328</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6...v6.0.1">https://github.com/actions/checkout/compare/v6...v6.0.1</a></p> <h2>v6.0.0</h2> <h2>What's Changed</h2> <ul> <li>Update README to include Node.js 24 support details and requirements by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li> <li>Persist creds to a separate file by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li> <li>v6-beta by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2298">actions/checkout#2298</a></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <h2>v7.0.0</h2> <ul> <li>Block checking out fork PR for pull_request_target and workflow_run by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li> <li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li> <li>Bump flatted from 3.3.1 to 3.4.2 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li> <li>Bump js-yaml from 4.1.0 to 4.2.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li> <li>Bump <code>@actions/core</code> and <code>@actions/tool-cache</code> and Remove uuid by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li> <li>upgrade module to esm and update dependencies by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li> <li>Bump the minor-npm-dependencies group across 1 directory with 3 updates by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li> </ul> <h2>v6.0.3</h2> <ul> <li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li> <li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> </ul> <h2>v6.0.2</h2> <ul> <li>Fix tag handling: preserve annotations and explicit fetch-tags by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li> </ul> <h2>v6.0.1</h2> <ul> <li>Add worktree support for persist-credentials includeIf by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li> </ul> <h2>v6.0.0</h2> <ul> <li>Persist creds to a separate file by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li> <li>Update README to include Node.js 24 support details and requirements by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li> </ul> <h2>v5.0.1</h2> <ul> <li>Port v6 cleanup to v5 by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li> </ul> <h2>v5.0.0</h2> <ul> <li>Update actions checkout to use node 24 by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li> </ul> <h2>v4.3.1</h2> <ul> <li>Port v6 cleanup to v4 by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li> </ul> <h2>v4.3.0</h2> <ul> <li>docs: update README.md by <a href="https://github.com/motss"><code>@motss</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li> <li>Add internal repos for checking out multiple repositories by <a href="https://github.com/mouismail"><code>@mouismail</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li> <li>Documentation update - add recommended permissions to Readme by <a href="https://github.com/benwells"><code>@benwells</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li> <li>Adjust positioning of user email note and permissions heading by <a href="https://github.com/joshmgross"><code>@joshmgross</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li> <li>Update README.md by <a href="https://github.com/nebuk89"><code>@nebuk89</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li> <li>Update CODEOWNERS for actions by <a href="https://github.com/TingluoHuang"><code>@TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li> <li>Update package dependencies by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li> </ul> <h2>v4.2.2</h2> <ul> <li><code>url-helper.ts</code> now leverages well-known environment variables by <a href="https://github.com/jww3"><code>@jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li> <li>Expand unit test coverage for <code>isGhes</code> by <a href="https://github.com/jww3"><code>@jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li> </ul> <h2>v4.2.1</h2> <ul> <li>Check out other refs/* by commit if provided, fall back to ref by <a href="https://github.com/orhantoy"><code>@orhantoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href=" |