Commit graph

238 commits

Author SHA1 Message Date
JD Davis
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))
([b485768](b4857685ff))
* **proxy/responses:** keep the Codex additional_tools carrier on the
wire ([#3194](https://github.com/headroomlabs-ai/headroom/issues/3194))
([1617f83](1617f839a1))
* **security:** validate caller-supplied upstreams on every resolution
path ([#3195](https://github.com/headroomlabs-ai/headroom/issues/3195))
([3e3c409](3e3c409436))
* skip cross-turn dedup pointers on OpenAI chat streaming
([#3191](https://github.com/headroomlabs-ai/headroom/issues/3191))
([9c30b62](9c30b62962))
* **wrap:** make the Serena pre-index stall budget configurable
([#3183](https://github.com/headroomlabs-ai/headroom/issues/3183))
([202c189](202c1895e1))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-21 23:29:15 -07:00
JD Davis
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 &gt;= 0.149.0 additional_tools into
top-level tools
([#3186](https://github.com/headroomlabs-ai/headroom/issues/3186))
([25ca580](25ca580825))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-21 14:57:44 -07:00
JD Davis
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))
([397803a](397803a942))
* **kompress:** accept ccr_original on the remote compressor
([#3162](https://github.com/headroomlabs-ai/headroom/issues/3162))
([45cb1b9](45cb1b9c48))
* **proxy:** count output tokens from the stream's text, not its wire
size ([#3163](https://github.com/headroomlabs-ai/headroom/issues/3163))
([4006964](4006964a03))


### Dependencies

* bump ai from 6.0.138 to 7.0.59 in /sdk/typescript
([#2281](https://github.com/headroomlabs-ai/headroom/issues/2281))
([0891062](08910624fb))
* bump ai from 6.0.149 to 7.0.59 in /docs
([#2277](https://github.com/headroomlabs-ai/headroom/issues/2277))
([f7e5d37](f7e5d37f52))
* bump md-5 from 0.10.6 to 0.11.0
([#3146](https://github.com/headroomlabs-ai/headroom/issues/3146))
([c6dd823](c6dd823384))
* bump ruff from 0.16.2 to 0.16.3 in the pip-minor-patch group
([#3143](https://github.com/headroomlabs-ai/headroom/issues/3143))
([c8db13d](c8db13d5ad))
* bump the cargo-minor-patch group with 8 updates
([#3145](https://github.com/headroomlabs-ai/headroom/issues/3145))
([9c14e3a](9c14e3aa95))
* bump tiktoken-rs from 0.11.0 to 0.12.0
([#3147](https://github.com/headroomlabs-ai/headroom/issues/3147))
([a307c11](a307c11109))
* bump tokenizers from 0.22.2 to 0.23.1
([#3149](https://github.com/headroomlabs-ai/headroom/issues/3149))
([6e2e10f](6e2e10f67a))
* bump typescript from 5.9.3 to 7.0.2 in /plugins/openclaw
([#2279](https://github.com/headroomlabs-ai/headroom/issues/2279))
([85774fc](85774fcb70))
* bump typescript from 5.9.3 to 7.0.2 in /plugins/opencode
([#2280](https://github.com/headroomlabs-ai/headroom/issues/2280))
([a382137](a382137844))
* update mcp requirement from &lt;2.0.0,&gt;=1.28.1 to
&gt;=1.28.1,&lt;3.0.0
([#3144](https://github.com/headroomlabs-ai/headroom/issues/3144))
([6928d19](6928d1932c))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-20 23:09:18 -07:00
dependabot[bot]
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="cbdbeee90d"><code>cbdbeee</code></a>
Version Packages (<a
href="https://github.com/vercel/ai/tree/HEAD/packages/ai/issues/18645">#18645</a>)</li>
<li><a
href="63db19387b"><code>63db193</code></a>
Version Packages (<a
href="https://github.com/vercel/ai/tree/HEAD/packages/ai/issues/18587">#18587</a>)</li>
<li><a
href="72ad23fd56"><code>72ad23f</code></a>
fix: ToolLoopAgent settings-level timeouts being ignored by generate and
stre...</li>
<li><a
href="81cd0263f3"><code>81cd026</code></a>
perf: make zod imports tree-shakeable (<a
href="https://github.com/vercel/ai/tree/HEAD/packages/ai/issues/18304">#18304</a>)</li>
<li><a
href="ad6a65001d"><code>ad6a650</code></a>
feat(video): allow <code>aspectRatio: 'adaptive'</code> on generateVideo
(<a
href="https://github.com/vercel/ai/tree/HEAD/packages/ai/issues/18586">#18586</a>)</li>
<li><a
href="ae26160e2b"><code>ae26160</code></a>
Version Packages (<a
href="https://github.com/vercel/ai/tree/HEAD/packages/ai/issues/18566">#18566</a>)</li>
<li><a
href="2f04a5e2ac"><code>2f04a5e</code></a>
Version Packages (<a
href="https://github.com/vercel/ai/tree/HEAD/packages/ai/issues/18560">#18560</a>)</li>
<li><a
href="89080c861b"><code>89080c8</code></a>
feat (provider/gateway): support async video operations
(doStart/doStatus) on...</li>
<li><a
href="25c91200ce"><code>25c9120</code></a>
feat: expose provider metadata in language model call end callbacks (<a
href="https://github.com/vercel/ai/tree/HEAD/packages/ai/issues/18100">#18100</a>)</li>
<li><a
href="79d619530c"><code>79d6195</code></a>
fix: resumed chat streams updating state after cancellation or a newer
resume...</li>
<li>Additional commits viewable in <a
href="https://github.com/vercel/ai/commits/ai@7.0.59/packages/ai">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for ai since your current version.</p>
</details>
<br />


> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-08-20 21:37:35 -05:00
dependabot[bot]
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 &quot;mcp[cli]&quot;
# or
uv add &quot;mcp[cli]&quot;
</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>&lt;2</code> upper
bound on your requirement (for example
<code>mcp&gt;=1.28,&lt;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="6f69a3758e"><code>6f69a37</code></a>
Present v2 as the stable release across the README, docs, and policies
(<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3178">#3178</a>)</li>
<li><a
href="78e6fbb7e4"><code>78e6fbb</code></a>
Serve v2 docs at the site root, with permanent per-major paths (<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3176">#3176</a>)</li>
<li><a
href="af06330a31"><code>af06330</code></a>
Remove unused StreamableHTTPTransport.get_session_id() (<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3205">#3205</a>)</li>
<li><a
href="68ca87e20b"><code>68ca87e</code></a>
Document the two-line release process for stable v2 (<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3179">#3179</a>)</li>
<li><a
href="c9c431b71a"><code>c9c431b</code></a>
Expose the middleware chain on MCPServer and stop sending unrequested
change ...</li>
<li><a
href="528e366558"><code>528e366</code></a>
Fail fast on server-to-client requests in JSON-response mode instead of
hangi...</li>
<li><a
href="27f5cc7a46"><code>27f5cc7</code></a>
Remove unused mcpserver.exceptions.ValidationError (<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3199">#3199</a>)</li>
<li><a
href="89c5e700f2"><code>89c5e70</code></a>
Gate log notifications on the per-request log-level opt-in at 2026-07-28
(<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3198">#3198</a>)</li>
<li><a
href="b61ce388dd"><code>b61ce38</code></a>
docs: fix off-by-one hl_lines in apps.md (<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3196">#3196</a>)</li>
<li><a
href="b7c9a916d6"><code>b7c9a91</code></a>
Add mcp.types as a permanent alias for mcp_types (<a
href="https://redirect.github.com/modelcontextprotocol/python-sdk/issues/3190">#3190</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/modelcontextprotocol/python-sdk/compare/v1.28.1...v2.0.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-08-20 20:58:06 -05:00
JD Davis
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>
2026-08-20 17:20:18 -07:00
Tejas Chopra
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>
2026-08-20 11:45:59 -07:00
Tejas Chopra
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.
2026-08-20 09:02:44 -05:00
JD Davis
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))
([3077ac8](3077ac81e8))
* **proxy:** let extensions report cost savings and their own latency
([#3051](https://github.com/headroomlabs-ai/headroom/issues/3051))
([f9807fd](f9807fd69e))
* **proxy:** unify savings attribution across stats, perf, metrics, and
dashboard
([1b0b0b8](1b0b0b89a4)),
closes [#2976](https://github.com/headroomlabs-ai/headroom/issues/2976)
* **wrap/claude:** make the --1m fallback model configurable via
HEADROOM_1M_MODEL
([#2983](https://github.com/headroomlabs-ai/headroom/issues/2983))
([2a84725](2a8472525d))


### Bug Fixes

* **anthropic:** honor the [1m] 1M-context tier, and price it correctly
([#3073](https://github.com/headroomlabs-ai/headroom/issues/3073))
([6d2254d](6d2254dfb5))
* **ccr:** make --no-ccr disable server-side response handling too
([#3101](https://github.com/headroomlabs-ai/headroom/issues/3101))
([131b119](131b119c05)),
closes [#3082](https://github.com/headroomlabs-ai/headroom/issues/3082)
* **ccr:** make StreamingCCRHandler work on OpenAI streams
([#3069](https://github.com/headroomlabs-ai/headroom/issues/3069))
([7ef736f](7ef736fb1a))
* **ccr:** only buffer a stream when a marker is actually redeemable
([#3092](https://github.com/headroomlabs-ai/headroom/issues/3092))
([c502087](c502087db7))
* **ccr:** re-inject headroom_retrieve when history references it on the
sessionless path
([942af56](942af56f11))
* **ccr:** relay a successful upstream turn when post-processing fails
([#3094](https://github.com/headroomlabs-ai/headroom/issues/3094))
([0ec73fa](0ec73faa28))
* **ccr:** send Accept: application/json on a buffered stream:false turn
([#3102](https://github.com/headroomlabs-ai/headroom/issues/3102))
([139c7cb](139c7cbdde)),
closes [#3078](https://github.com/headroomlabs-ai/headroom/issues/3078)
* **ccr:** verify a scanned marker's hash before advertising it
([#2908](https://github.com/headroomlabs-ai/headroom/issues/2908))
([41dab2d](41dab2d099))
* **ci:** prevent native detector from hanging test shards
([#2996](https://github.com/headroomlabs-ai/headroom/issues/2996))
([a708c05](a708c0571e))
* **ci:** scope the release credential and stop persisting it to disk
([#3062](https://github.com/headroomlabs-ai/headroom/issues/3062))
([ac8646a](ac8646aa3c))
* **ci:** unjam release and Docker publishing
([#2958](https://github.com/headroomlabs-ai/headroom/issues/2958))
([e269afb](e269afb935))
* **claude:** reject conflicting auth before proxy startup
([#2993](https://github.com/headroomlabs-ai/headroom/issues/2993))
([2d88e31](2d88e31a40))
* **cli/install:** resolve the deployment profile instead of dead-ending
on default
([#2832](https://github.com/headroomlabs-ai/headroom/issues/2832))
([8252619](82526191a1))
* **cli:** stop the macOS malloc re-exec replacing an embedder's process
([#3064](https://github.com/headroomlabs-ai/headroom/issues/3064))
([96c25f5](96c25f5181))
* **copilot:** route VS Code inline completions to Copilot, not OpenAI
([#3077](https://github.com/headroomlabs-ai/headroom/issues/3077))
([204e751](204e751d2f))
* **copilot:** send VS Code inline completions to the host that serves
them ([#3112](https://github.com/headroomlabs-ai/headroom/issues/3112))
([b77d612](b77d612913))
* **deps:** bump datasets past PYSEC-2026-3716
([#3136](https://github.com/headroomlabs-ai/headroom/issues/3136))
([df6ff6b](df6ff6bd5b))
* **deps:** clear the two Rust advisories and make cargo audit blocking
([#3121](https://github.com/headroomlabs-ai/headroom/issues/3121))
([93c474e](93c474e84b))
* **deps:** raise the GitPython floor to 3.1.58 to clear 9 open
advisories
([#3120](https://github.com/headroomlabs-ai/headroom/issues/3120))
([8156d4d](8156d4dc3a))
* **docker:** publish compose ports on loopback only
([#3061](https://github.com/headroomlabs-ai/headroom/issues/3061))
([481e0b8](481e0b83d5))
* **docker:** ship Bedrock auth and current registry
([#2982](https://github.com/headroomlabs-ai/headroom/issues/2982))
([eafdf11](eafdf11a2c))
* **doctor:** surface that Claude Desktop agent sessions bypass the
proxy ([#2987](https://github.com/headroomlabs-ai/headroom/issues/2987))
([be5b26d](be5b26d807))
* **install:** consolidate Windows fallback and cleanup safety
([#2980](https://github.com/headroomlabs-ai/headroom/issues/2980))
([ddd2a25](ddd2a259ec))
* **install:** honor HEADROOM_PORT in install apply and deploy
([#3085](https://github.com/headroomlabs-ai/headroom/issues/3085))
([58f28dc](58f28dc7a6))
* **install:** stop the PowerShell installer leaking temp dirs into the
real user PATH
([#2985](https://github.com/headroomlabs-ai/headroom/issues/2985))
([ddd9f76](ddd9f76729))
* **learn:** include stdout in CLI failure messages, not just stderr
([#3080](https://github.com/headroomlabs-ai/headroom/issues/3080))
([c5563d3](c5563d3a7d))
* **mcp:** restore SDK v1 compatibility cap
([#2978](https://github.com/headroomlabs-ai/headroom/issues/2978))
([6077e5a](6077e5a149))
* **memory:** sanitize entity_refs to prevent dict-shaped entries
crashing search
([#2951](https://github.com/headroomlabs-ai/headroom/issues/2951))
([2d1e96b](2d1e96b85c))
* **onnx:** enforce Rust API-24 runtime compatibility
([#2979](https://github.com/headroomlabs-ai/headroom/issues/2979))
([a3fe5cb](a3fe5cb65b))
* **openclaw-plugin:** circuit breaker + per-request timeout for proxy
resilience
([#639](https://github.com/headroomlabs-ai/headroom/issues/639))
([6576ef6](6576ef639c))
* **opencode:** send x-headroom-project header on all proxied requests
([#2868](https://github.com/headroomlabs-ai/headroom/issues/2868))
([eeb038b](eeb038bc0c))
* **policy:** price net-cost mutations with the 1h cache-write tier
([#2780](https://github.com/headroomlabs-ai/headroom/issues/2780))
([ef7e07e](ef7e07e0f5))
* **providers:** don't crash on a non-object HEADROOM_MODEL_LIMITS /
models.json
([#3089](https://github.com/headroomlabs-ai/headroom/issues/3089))
([3ed8f76](3ed8f76019))
* **proxy/anthropic:** don't buffer a CCR stream when passthrough
discards the stream flip
([#2953](https://github.com/headroomlabs-ai/headroom/issues/2953))
([f1c34d3](f1c34d336c))
* **proxy/anthropic:** don't replay recorded prefix over live history
([#3026](https://github.com/headroomlabs-ai/headroom/issues/3026))
([#3052](https://github.com/headroomlabs-ai/headroom/issues/3052))
([c16be9b](c16be9bbbe))
* **proxy/anthropic:** repair headroom_retrieve history references the
tools array cannot support
([#2876](https://github.com/headroomlabs-ai/headroom/issues/2876))
([7de3573](7de35739c6))
* **proxy/anthropic:** stop answering a non-streaming turn with an event
stream
([#3142](https://github.com/headroomlabs-ai/headroom/issues/3142))
([0e26fb8](0e26fb80de))
* **proxy/cache:** strip cache_control from messages in the semantic
cache key
([#3086](https://github.com/headroomlabs-ai/headroom/issues/3086))
([2cae0f8](2cae0f8eaf))
* **proxy/gemini:** guard CCR continuation usage against present-null
counts
([#3035](https://github.com/headroomlabs-ai/headroom/issues/3035))
([a01897c](a01897c791))
* **proxy/openai:** propagate provider usage on the Responses
WS-&gt;HTTP fallback
([#2988](https://github.com/headroomlabs-ai/headroom/issues/2988))
([536c949](536c949a69))
* **proxy:** adapt 200 SSE upstream replies on buffered /v1/responses
instead of 502
([#2622](https://github.com/headroomlabs-ai/headroom/issues/2622))
([d76fce0](d76fce04a3))
* **proxy:** align signed-thinking wire accounting
([#3015](https://github.com/headroomlabs-ai/headroom/issues/3015))
([b3f4436](b3f443636d))
* **proxy:** complete stateless Responses and buffered CCR lifecycle
([#2997](https://github.com/headroomlabs-ai/headroom/issues/2997))
([8a1d38b](8a1d38bc5d))
* **proxy:** guard feedback endpoints and add CSRF checks to loopback
writes
([#3060](https://github.com/headroomlabs-ai/headroom/issues/3060))
([a6ab359](a6ab359a5d))
* **proxy:** keep prefixed core tools resident
([#3046](https://github.com/headroomlabs-ai/headroom/issues/3046))
([2f4d001](2f4d001c9f))
* **proxy:** preserve Codex WebSocket model attribution
([#3029](https://github.com/headroomlabs-ai/headroom/issues/3029))
([a06a51e](a06a51eca6))
* **proxy:** relocate stray system-role messages to the top-level system
param ([#765](https://github.com/headroomlabs-ai/headroom/issues/765))
([#1357](https://github.com/headroomlabs-ai/headroom/issues/1357))
([9fde127](9fde127534))
* **proxy:** restore the buffered-CCR heartbeat behind a grace window
([#3091](https://github.com/headroomlabs-ai/headroom/issues/3091))
([a29d201](a29d2015e5))
* **proxy:** scope the signed-thinking lock to blocks that actually
changed
([#3124](https://github.com/headroomlabs-ai/headroom/issues/3124))
([17522fb](17522fb0a1))
* **proxy:** stop a lone surrogate turning a thinking body into a 500
([#3134](https://github.com/headroomlabs-ai/headroom/issues/3134))
([284ff31](284ff31947))
* **proxy:** stop cached responses replaying the producing turn's wire
framing
([#3024](https://github.com/headroomlabs-ai/headroom/issues/3024))
([9d37059](9d370592b0))
* **proxy:** stop operator secrets following a client-chosen upstream
([#3122](https://github.com/headroomlabs-ai/headroom/issues/3122))
([05f5ef4](05f5ef47cb))
* **proxy:** tune macOS libmalloc and trim allocator pages so long-lived
RSS stays bounded
([#2879](https://github.com/headroomlabs-ai/headroom/issues/2879))
([6d87825](6d87825f62))
* **reporting:** show net vs gross savings, real skip thresholds, and
the effective profile
([#3123](https://github.com/headroomlabs-ai/headroom/issues/3123))
([250ede2](250ede2f7f))
* tool_search_tool_regex deferred and falsely resolved on
direct-Anthropic path
([#2971](https://github.com/headroomlabs-ai/headroom/issues/2971))
([8ea87e7](8ea87e7804))
* **vscode:** persist compatible Claude modes and route Copilot CAPI
([#2986](https://github.com/headroomlabs-ai/headroom/issues/2986))
([1aa701a](1aa701adaa))
* **wrap:** set xAI upstream for grok-build proxy
([#2772](https://github.com/headroomlabs-ai/headroom/issues/2772))
([c831081](c8310819a4))
* **wrap:** stop the Serena pre-index stalling the launch path for 300s
([#2945](https://github.com/headroomlabs-ai/headroom/issues/2945))
([6147883](6147883d5e))
* **wrap:** verify proxy deps before mutating Codex config
([#1628](https://github.com/headroomlabs-ai/headroom/issues/1628))
([b7f342c](b7f342c153))


### Performance Improvements

* **perf:** skip rotated logs outside the requested window
([#3081](https://github.com/headroomlabs-ai/headroom/issues/3081))
([6c9f41e](6c9f41e08c))


### Dependencies

* bump axum from 0.7.9 to 0.8.9
([#2966](https://github.com/headroomlabs-ai/headroom/issues/2966))
([5731be7](5731be7e68))
* bump criterion from 0.5.1 to 0.8.2
([#2965](https://github.com/headroomlabs-ai/headroom/issues/2965))
([b30f339](b30f339d69))
* bump ruff from 0.15.22 to 0.16.2 in the pip-minor-patch group across 1
directory
([#2962](https://github.com/headroomlabs-ai/headroom/issues/2962))
([ff17961](ff17961cd7))
* bump sha2 from 0.10.9 to 0.11.0
([#2288](https://github.com/headroomlabs-ai/headroom/issues/2288))
([322425c](322425c43b))
* bump the cargo-minor-patch group across 1 directory with 4 updates
([#2964](https://github.com/headroomlabs-ai/headroom/issues/2964))
([888a9f4](888a9f4e14))
* bump tokio-tungstenite from 0.24.0 to 0.30.0
([#2967](https://github.com/headroomlabs-ai/headroom/issues/2967))
([bbe9013](bbe901319d))
* update mcp requirement from &lt;2.0.0,&gt;=1.28.1 to
&gt;=1.28.1,&lt;3.0.0
([#2963](https://github.com/headroomlabs-ai/headroom/issues/2963))
([d6fb536](d6fb5365f6))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-19 21:14:59 -07:00
Tejas Chopra
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` @ `b77d6129`.
**`cargo` is not installed on this machine** — see below.
- Exact command / steps: (1) parsed `uv.lock`, `Cargo.lock` and all four
`package-lock.json` files into 1,463 unique (ecosystem, name, version)
tuples and queried `api.osv.dev/v1/querybatch`, then pulled full records
for every hit; (2) walked `Cargo.lock` to find which workspace crates
actually reach `h2`, `crossbeam-epoch` and `paste`; (3) fetched both
crates' dependency lists from the crates.io API at the old and new
versions and compared them; (4) downloaded both `.crate` tarballs and
computed SHA-256 locally.
- Observed result: both bumps are patch-level with **byte-identical
dependency sets**, so the edited `Cargo.lock` is exactly what `cargo
update -p h2 -p crossbeam-epoch` would produce, and both checksums match
the real tarballs. `h2` 0.4.16 was published 2026-08-17, which is also
why Dependabot has not raised it yet.
- Not tested: I could not run `cargo audit`, `cargo build` or the test
suite locally — cargo is not installed here. **The lock edit is
hand-written, so CI is the real verification**, and it is well covered:
the `rust` workflow's `test`, `build`, `parity` and now-blocking `audit`
jobs all consume this lock and will fail on a bad checksum or an
unresolvable graph. I would not merge this on green-by-assertion; it
needs the `rust` jobs actually green.

## Runtime Rollout Safety

- Rollout-managed feature(s): None.
- Minimum rollout channel: n/a
- Stable/default behavior changed: No runtime behavior changes. CI
becomes stricter: `cargo audit` can now fail a build.
- Kill switch / disable path: re-adding `continue-on-error: true`
restores the previous (useless) behavior.
- Unsafe override required: No.
- Qualification impact: A newly-disclosed RUSTSEC advisory will now turn
the nightly Rust run red instead of being silently absorbed. That is the
intent, but it does mean advisories become someone's problem on
disclosure day — the escape hatch is a documented entry in
`.cargo/audit.toml`.
- Rollback path: Revert the commit.

## Review Readiness

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

## Additional Notes

Deliberately **not** bundled here, each worth its own change:

- **`npm` has no audit gate at all** — no `npm audit` anywhere in
`.github/`. Current npm exposure is only `nanoid` 3.3.17
(GHSA-2v37-7h3g-55p8) in three lockfiles, all `dev: true`, which GitHub
auto-dismissed correctly. Low stakes today, but the gate is absent
rather than passing.
- **`pip-audit` only audits `--extra all`**, which excludes the
integration extras (`crewai`, `agno`, `autogen`, `langchain`, `strands`,
`bedrock`, `memory-stack`, `sandbox`). Every Python advisory currently
open against this repo lives in exactly that blind spot — GitPython via
`agno` (#3120), chromadb and json-repair via `crewai`. Dependabot
catches them because it scans the whole lock; the CI gate structurally
cannot.
- **Dependabot's `docker` ecosystem is configured for `directory: /`
only**, so the five non-root Dockerfiles get no base-image updates.

Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local>
2026-08-19 14:19:46 -07:00
Tejas Chopra
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>
2026-08-16 19:05:32 -07:00
JD Davis
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>
2026-08-13 15:05:45 -05:00
JD Davis
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 `93f2d7a2`; this PR prevents the artifact collision in subsequent
Docker runs.
2026-08-12 23:43:04 -05:00
JD Davis
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 `8e793a80`, all 52
completed GitHub checks passed; the only other conclusions are expected
skips and superseded governance jobs.

## Real Behavior Proof

- **Environment:** Windows checkout on Python 3.13.3 and the current
Rust workspace, based on upstream `main` at `93f2d7a2`.
- **Exact command / steps:** Exercised canary and beta feature requests
through CLI status, Python `/stats.rollout`, Rust `/rollout/status`,
multi-worker payload round trips, loopback `/admin/runtime-env`, real
proxy request shaping before/after hot reload, installer manifest
generation, and shared Python/Rust policy vectors.
- **Observed result:** Stable blocks unstable requests; disable wins
over explicit/default/legacy/unsafe paths; unsafe state reports
`qualification_eligible=false`; worker handoff rejects tampering;
running output shaping changes only when the effective beta policy
permits it; explicit blocked flags fail with actionable diagnostics.
- **Not tested:** Live production traffic requiring provider
credentials, or future artifact qualification/promotion automation
(intentionally out of scope).

## Runtime Rollout Safety

- **Rollout-managed features:** Python `tool_result_interceptors`,
`proxy_output_shaper`, `read_maturation`; Rust `native_bedrock`,
`openai_responses_streaming`, `canary_probe`.
- **Minimum rollout channel:** Registry-defined per feature; process
default is `stable`.
- **Stable/default behavior changed:** No unstable feature becomes
enabled by default. Explicit blocked CLI flags now fail instead of
silently doing nothing.
- **Kill switch / disable path:**
`HEADROOM_DISABLE_FEATURES=<comma-separated feature names>`; explicit
disable has highest precedence, including over the unsafe override.
- **Unsafe override required:** No.
`HEADROOM_UNSAFE_ALLOW_UNSTABLE_FEATURES=1` is break-glass only and
makes qualification evidence ineligible.
- **Qualification impact:** Adds machine-readable policy/snapshot
identities and eligibility; does not implement qualification itself.
- **Rollback path:** Set the named disable list for operational
rollback, lower the channel, or revert this PR.

## Review Readiness

- [x] I have performed a full diff 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 hard-to-understand areas
- [x] I have made corresponding documentation changes
- [x] My changes generate no new warnings
- [x] I added tests that reproduce and prevent every regression fixed
during review
- [x] New and existing affected tests pass locally
- [x] I did **not** edit `CHANGELOG.md`; release-please generates it
from the Conventional Commit PR title

## Additional Notes

Out of scope: artifact candidates, benchmark orchestration,
qualification manifests/gates, promotion automation, release branches,
publication guards, and release-risk classification. Those workflows can
consume the rollout registry digest, runtime snapshot digest, decision
reasons, and qualification eligibility through supported black-box
interfaces.

---------

Co-authored-by: JD Davis <jd@jds-macbook-air.tail2a279.ts.net>
Co-authored-by: JD Davis <jd@JDH-AIR-00.local>
2026-08-12 23:16:54 -05:00
Tejas Chopra
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))
([7940c05](7940c05ebf))
* **beacon:** hourly R2 compaction, per-strategy savings, and a stack
that reports
([#2853](https://github.com/headroomlabs-ai/headroom/issues/2853))
([e0870ef](e0870ef931))
* **cli,pricing:** add CLI extension seam and prompt-cache TTL pricing
([#2802](https://github.com/headroomlabs-ai/headroom/issues/2802))
([6ec3e34](6ec3e3478a))


### Bug Fixes

* **anthropic:** strip first-party tool search on custom upstreams
([#2539](https://github.com/headroomlabs-ai/headroom/issues/2539))
([7f6950b](7f6950be34))
* **backends/anyllm:** convert Anthropic tools and tool_choice to OpenAI
shape
([0d6866b](0d6866b91a))
* **backends/anyllm:** stream tool_use blocks and map finish_reason on
the streaming path
([e4904e2](e4904e23a6))
* **backends/litellm:** None-guard core token counts in OpenAI usage
block ([#2324](https://github.com/headroomlabs-ai/headroom/issues/2324))
([12f9f58](12f9f58cb3))
* **beacon:** report all-layers savings, not context-compression only
([#2796](https://github.com/headroomlabs-ai/headroom/issues/2796))
([e9a24f3](e9a24f3ec1))
* **beacon:** split session failures by status code
([#2815](https://github.com/headroomlabs-ai/headroom/issues/2815))
([2954e37](2954e37048))
* **cache:** bound compression cache bookkeeping
([0ae948c](0ae948c151))
* **cache:** enforce Anthropic's 1h-before-5m cache_control ordering
before forwarding
([#2941](https://github.com/headroomlabs-ai/headroom/issues/2941))
([3752458](3752458022))
* **cache:** mirror client cache_control positions instead of
single-marker consolidation
([def3d76](def3d76e5a))
* **cache:** stabilize Anthropic block-growing lineages
([#2917](https://github.com/headroomlabs-ai/headroom/issues/2917))
([1a04c95](1a04c957f5))
* **ccr:** avoid injecting tool on chat streaming
([d0c1f5b](d0c1f5b8ad))
* **ccr:** preserve exact SQLite TTL boundary
([#2669](https://github.com/headroomlabs-ai/headroom/issues/2669))
([d0a86d4](d0a86d409f))
* **ccr:** report embedded hashes from compress endpoint
([#717](https://github.com/headroomlabs-ai/headroom/issues/717))
([685ebe4](685ebe457d))
* **ccr:** resolve &lt;&lt;ccr:...&gt;&gt; markers inline when no
retrieve-tool path exists
([#2512](https://github.com/headroomlabs-ai/headroom/issues/2512))
([ce8ce83](ce8ce8313f))
* **ccr:** tolerate null/malformed OpenAI data in response handling
([#2467](https://github.com/headroomlabs-ai/headroom/issues/2467))
([e583e08](e583e082d8))
* **ci:** publish latest from the root Docker manifest
([#2252](https://github.com/headroomlabs-ai/headroom/issues/2252))
([5568d73](5568d738af))
* **claude:** stop forcing tool search on Foundry
([#2477](https://github.com/headroomlabs-ai/headroom/issues/2477))
([7981396](798139608c))
* **cli/update:** let install ownership win over bare /.dockerenv so
venv installs self-update
([#2830](https://github.com/headroomlabs-ai/headroom/issues/2830))
([7092b53](7092b53c46))
* **codex:** route alpha search through the Codex backend
([#2538](https://github.com/headroomlabs-ai/headroom/issues/2538))
([a540eb2](a540eb2c61))
* **content-router:** protect custom-tag blocks before mixed-content
section split
([d7bc1e2](d7bc1e275f))
* **deps:** bump h2 to 4.4.1 for CVE-2026-71554
([#2839](https://github.com/headroomlabs-ai/headroom/issues/2839))
([564e0a8](564e0a8d0f))
* **deps:** enforce audited transitive dependency floors
([#2791](https://github.com/headroomlabs-ai/headroom/issues/2791))
([64e2039](64e203931b))
* **doctor:** flag `ollama launch claude` proxy bypass instead of
misdirecting
([#2566](https://github.com/headroomlabs-ai/headroom/issues/2566))
([7f24d69](7f24d695ee))
* emit SSE ping before message_start on Bedrock streaming path (issue
[#902](https://github.com/headroomlabs-ai/headroom/issues/902))
([#1080](https://github.com/headroomlabs-ai/headroom/issues/1080))
([4dab254](4dab254d52))
* **gemini:** resolve native CCR retrieval calls
([#2253](https://github.com/headroomlabs-ai/headroom/issues/2253))
([2483f57](2483f57002))
* **health:** label kompress as degraded/optional when not yet loaded
([#2865](https://github.com/headroomlabs-ai/headroom/issues/2865))
([8949371](89493714d2))
* **image:** decouple routing types from trained_router so importing the
compressor doesn't import torch
([#2513](https://github.com/headroomlabs-ai/headroom/issues/2513))
([#2537](https://github.com/headroomlabs-ai/headroom/issues/2537))
([d7cf981](d7cf981093))
* **install/windows:** register persistent-task from S4U hidden XML
([#2453](https://github.com/headroomlabs-ai/headroom/issues/2453))
([#2459](https://github.com/headroomlabs-ai/headroom/issues/2459))
([1edaeb8](1edaeb8b76))
* **install:** don't crash the PowerShell installer when $PROFILE is
unset ([#2469](https://github.com/headroomlabs-ai/headroom/issues/2469))
([fc5c4e2](fc5c4e239c))
* **install:** trust Docker bridge for dashboard metadata
([e044139](e044139001))
* **install:** use --userns=keep-id under Podman so bind-mount writes
don't fail
([#2846](https://github.com/headroomlabs-ai/headroom/issues/2846))
([3488f8d](3488f8d4b5))
* **learn/gemini:** stop double-counting session tokens
([#2230](https://github.com/headroomlabs-ai/headroom/issues/2230))
([29d8a5e](29d8a5e563))
* **learn/grok:** detect a Windows absolute project path
([#2283](https://github.com/headroomlabs-ai/headroom/issues/2283))
([e240df2](e240df2b69))
* **learn:** stop classifying a successful exit code 0 as an error
([#2289](https://github.com/headroomlabs-ai/headroom/issues/2289))
([a24fe7d](a24fe7dcbf))
* **litellm:** add async_post_call_success_hook to HeadroomCallback
([#1322](https://github.com/headroomlabs-ai/headroom/issues/1322))
([3107994](3107994aed))
* **litellm:** don't forward a caller key the target cannot accept
([#2883](https://github.com/headroomlabs-ai/headroom/issues/2883))
([2f2950a](2f2950a626))
* **memory:** bound the TrafficLearner pending-pattern accumulator
(memory leak)
([#2579](https://github.com/headroomlabs-ai/headroom/issues/2579))
([1f5feff](1f5fefffd3))
* **memory:** close DirectMem0 resources
([6596182](65961827cf))
* **memory:** close MCP backend on shutdown
([4bd8ecd](4bd8ecd1e3))
* **memory:** don't crash inline memory extraction on a non-object
&lt;memory&gt; block
([#2470](https://github.com/headroomlabs-ai/headroom/issues/2470))
([e00c6ff](e00c6ff81c))
* **memory:** keep vector metadata in sync
([#2295](https://github.com/headroomlabs-ai/headroom/issues/2295))
([c471800](c471800e8e))
* **memory:** make explicit-project and user store keys
collision-resistant
([#2231](https://github.com/headroomlabs-ai/headroom/issues/2231))
([f840d5f](f840d5f2fe))
* **memory:** skip &lt;system-reminder&gt; blocks when building the
retrieval query
([#2195](https://github.com/headroomlabs-ai/headroom/issues/2195))
([#2541](https://github.com/headroomlabs-ai/headroom/issues/2541))
([4e5a67a](4e5a67a342))
* **memory:** sync FTS5 and vector indexes on CLI
delete/edit/prune/purge
([fd4628d](fd4628d821))
* **oauth2:** make repository lint checks pass
([c85abf7](c85abf7a87))
* **observability:** aggregate tool savings in OTEL
([#2936](https://github.com/headroomlabs-ai/headroom/issues/2936))
([941c25d](941c25d31e))
* **onnx:** stop ONNX thread pools from spinning idle cores
([#2495](https://github.com/headroomlabs-ai/headroom/issues/2495))
([#2540](https://github.com/headroomlabs-ai/headroom/issues/2540))
([5c561bd](5c561bd913))
* **openai:** skip Responses tool-search deferral for clients that
cannot execute it
([#2696](https://github.com/headroomlabs-ai/headroom/issues/2696))
([54ea28d](54ea28d983))
* **opencode:** ship the transport hook-shim so wheel installs route
Node child traffic
([702dbc5](702dbc5902))
* **providers/anthropic:** don't crash token estimation on null
tool_calls
([#2472](https://github.com/headroomlabs-ai/headroom/issues/2472))
([08466f3](08466f3cae))
* **providers/openai:** bound tiktoken vocab loads with the guarded
loader
([#2554](https://github.com/headroomlabs-ai/headroom/issues/2554))
([0805e8e](0805e8e410))
* **proxy/anthropic:** inject headroom_retrieve whenever a CCR marker is
present, not only for new markers
([#2848](https://github.com/headroomlabs-ai/headroom/issues/2848))
([3808f60](3808f60ca6))
* **proxy/anthropic:** None-guard usage token counts on the direct
buffered path
([#2434](https://github.com/headroomlabs-ai/headroom/issues/2434))
([2b5ee7c](2b5ee7cde8))
* **proxy/anthropic:** run tool-search history repair after turn hooks
([c6f9948](c6f99482e1))
* **proxy/batch:** don't crash an OpenAI batch on a valid-JSON
non-object line
([#2316](https://github.com/headroomlabs-ai/headroom/issues/2316))
([1f2c681](1f2c681c0b))
* **proxy/bedrock:** report uncached input tokens from backend usage,
not the live-zone count
([#2318](https://github.com/headroomlabs-ai/headroom/issues/2318))
([c19e412](c19e412b33))
* **proxy/gemini:** keep streaming-parity baseline so eligible_pct can't
exceed 100
([#2824](https://github.com/headroomlabs-ai/headroom/issues/2824))
([b97c7c6](b97c7c6e99))
* **proxy/metrics:** cap client-supplied model label cardinality
([#2480](https://github.com/headroomlabs-ai/headroom/issues/2480))
([e24a7e6](e24a7e66b9))
* **proxy/metrics:** escape label values in the Prometheus export
([#2463](https://github.com/headroomlabs-ai/headroom/issues/2463))
([6a53861](6a53861063))
* **proxy/openai:** don't crash the Responses memory tool loops on null
arguments
([#2273](https://github.com/headroomlabs-ai/headroom/issues/2273))
([a30db2c](a30db2cae4))
* **proxy/openai:** feed Codex WS traffic into the traffic learner
([#2334](https://github.com/headroomlabs-ai/headroom/issues/2334))
([f669149](f669149769))
* **proxy/openai:** run response hooks on Responses, and bill their
re-drives
([#2872](https://github.com/headroomlabs-ai/headroom/issues/2872))
([675d13f](675d13f08d))
* **proxy:** allow settings routes for trusted gateway/dashboard clients
([#2491](https://github.com/headroomlabs-ai/headroom/issues/2491))
([a5b0a8f](a5b0a8f4cc))
* **proxy:** cache litellm model resolution to stop repeated Provider
List spam
([99f07e7](99f07e7bbd))
* **proxy:** cancel periodic TOIN task on shutdown
([739fdef](739fdef423))
* **proxy:** close the upstream stream when a streaming body is never
consumed
([0951663](0951663562))
* **proxy:** compress cache-mode cold starts and tag prefix-mismatch
passthrough
([#2365](https://github.com/headroomlabs-ai/headroom/issues/2365))
([aaeba0a](aaeba0a319))
* **proxy:** emit request log timestamps in UTC
([620028f](620028fa18))
* **proxy:** enable tool search by default and repair poisoned
transcripts
([#2807](https://github.com/headroomlabs-ai/headroom/issues/2807))
([0237cbf](0237cbffbb))
* **proxy:** gate mid-turn message coalescing to Claude Code clients
([#1643](https://github.com/headroomlabs-ai/headroom/issues/1643))
([a4bd2e6](a4bd2e62a5))
* **proxy:** give each Codex /v1/responses WS turn a unique request_id
([#2164](https://github.com/headroomlabs-ai/headroom/issues/2164))
([d02df10](d02df10758))
* **proxy:** graceful shutdown and reliable Ctrl+C exit
([#621](https://github.com/headroomlabs-ai/headroom/issues/621))
([17cdb18](17cdb185bc))
* **proxy:** guard telemetry and TOIN endpoints
([cde1513](cde1513c91))
* **proxy:** include tool_search_deferral savings in the savings ledger
([12149f7](12149f7446))
* **proxy:** pass through cross-region prefixed Bedrock model IDs
directly
([#2330](https://github.com/headroomlabs-ai/headroom/issues/2330))
([64cb46e](64cb46e24b))
* **proxy:** port session-sticky beta headers to the Rust proxy
([#2381](https://github.com/headroomlabs-ai/headroom/issues/2381))
([f6398a6](f6398a6476))
* **proxy:** preserve merged session and quarantine contracts
([#2943](https://github.com/headroomlabs-ai/headroom/issues/2943))
([039cd24](039cd2431a))
* **proxy:** preserve signed Anthropic thinking blocks on outbound
re-serialize
([#2254](https://github.com/headroomlabs-ai/headroom/issues/2254))
([dc163bc](dc163bcd1c))
* **proxy:** stop discarding compressed Codex WS later-frame payloads
([#2823](https://github.com/headroomlabs-ai/headroom/issues/2823))
([4ec416d](4ec416df88))
* **proxy:** time-cap the compression timeout-debt quarantine
([#2360](https://github.com/headroomlabs-ai/headroom/issues/2360))
([#2412](https://github.com/headroomlabs-ai/headroom/issues/2412))
([c5a08d2](c5a08d22e0))
* **proxy:** unwrap Hermes tool_call bridge in tool name map
([#2717](https://github.com/headroomlabs-ai/headroom/issues/2717))
([a97b824](a97b82413b))
* publish headroom-opencode in release workflow
([#2372](https://github.com/headroomlabs-ai/headroom/issues/2372))
([7859154](78591545ce))
* **settings:** accept documented HEADROOM_* env names as settings keys
([#2833](https://github.com/headroomlabs-ai/headroom/issues/2833))
([de9e052](de9e0523da))
* **subscription:** dedup transcript usage by message id
([#2340](https://github.com/headroomlabs-ai/headroom/issues/2340) token
inflation)
([#2408](https://github.com/headroomlabs-ai/headroom/issues/2408))
([74275b7](74275b7c3e))
* **toin:** bound private query and pattern retention
([8cd1380](8cd138039e))
* **tokenizer:** coerce non-string tool_call fields before counting
([#2801](https://github.com/headroomlabs-ai/headroom/issues/2801))
([b6f9877](b6f9877c78))
* **tokenizer:** price CJK in the Rust fixed-ratio estimator (Python
parity)
([#2260](https://github.com/headroomlabs-ai/headroom/issues/2260))
([6840153](6840153473))
* **transforms/adaptive-sizer:** honor max_k on small-input fast path
([#2319](https://github.com/headroomlabs-ai/headroom/issues/2319))
([8a90523](8a90523209))
* **transforms/smart_crusher:** don't crash on a tool call with a null
function
([#2232](https://github.com/headroomlabs-ai/headroom/issues/2232))
([3bb02f8](3bb02f8f75))
* Vertex model pricing shows $0.00 for versioned model names and
vertex:anthropic provider
([#2517](https://github.com/headroomlabs-ai/headroom/issues/2517))
([eb5b5e4](eb5b5e4198))
* **wrap/claude:** keep --1m effective when an explicit --model is
passed through
([c093bf1](c093bf11eb))
* **wrap/opencode:** verify the opencode binary before mutating config
([ae38486](ae384862a4))
* **wrap/serena:** install Serena from the serena-agent PyPI wheel, not
the git source
([d7b25ae](d7b25ae3bb))
* **wrap:** honor Copilot OAuth wire-api override and model default
([#2387](https://github.com/headroomlabs-ai/headroom/issues/2387))
([1db6d88](1db6d88ab4))
* **wrap:** serialize shared proxy startup
([#2946](https://github.com/headroomlabs-ai/headroom/issues/2946))
([e540d64](e540d64feb))
* **wrap:** stop the launch cwd from shadowing the installed package in
the proxy subprocess
([#2843](https://github.com/headroomlabs-ai/headroom/issues/2843))
([c49be26](c49be269a1))


### Performance Improvements

* cut hot-path latency 27% (token-count memo, startup preloads, JSON
scan memo)
([#2838](https://github.com/headroomlabs-ai/headroom/issues/2838))
([53af90d](53af90d68c))
* **proxy:** bound upstream calls and hot-path costs
([#2852](https://github.com/headroomlabs-ai/headroom/issues/2852))
([f624d3a](f624d3a00a))
* **subscription:** skip transcripts older than the window in
compute_window_tokens
([#2861](https://github.com/headroomlabs-ai/headroom/issues/2861))
([91d6bf3](91d6bf33cd))


### Dependencies

* bump brace-expansion from 5.0.7 to 5.0.9 in /docs
([#2751](https://github.com/headroomlabs-ai/headroom/issues/2751))
([56ee57b](56ee57be98))
* bump bytesize from 1.3.3 to 2.4.2
([#2286](https://github.com/headroomlabs-ai/headroom/issues/2286))
([6448545](6448545a7f))
* bump hf-hub from 0.4.3 to 0.5.0
([#2285](https://github.com/headroomlabs-ai/headroom/issues/2285))
([4925bf6](4925bf6a82))
* bump next from 16.2.10 to 16.3.0 in /docs
([#2750](https://github.com/headroomlabs-ai/headroom/issues/2750))
([0fd0b99](0fd0b996a4))
* bump postcss from 8.5.19 to 8.5.25 in /plugins/openclaw
([#2749](https://github.com/headroomlabs-ai/headroom/issues/2749))
([cd60ee9](cd60ee9ae8))
* bump postcss from 8.5.19 to 8.5.25 in /plugins/opencode
([#2748](https://github.com/headroomlabs-ai/headroom/issues/2748))
([ff4e016](ff4e0167bb))
* bump postcss from 8.5.19 to 8.5.25 in /sdk/typescript
([#2747](https://github.com/headroomlabs-ai/headroom/issues/2747))
([267c2bd](267c2bdcb5))
* bump postcss from 8.5.19 to 8.5.26 in /docs
([#2881](https://github.com/headroomlabs-ai/headroom/issues/2881))
([e6e5826](e6e5826423))
* bump ruff from 0.15.17 to 0.15.22 in the pip-minor-patch group
([#2501](https://github.com/headroomlabs-ai/headroom/issues/2501))
([ecf130d](ecf130d3ac))
* bump rusqlite from 0.32.1 to 0.40.1
([#2287](https://github.com/headroomlabs-ai/headroom/issues/2287))
([522faa1](522faa1a59))
* bump the cargo-minor-patch group across 1 directory with 22 updates
([#2916](https://github.com/headroomlabs-ai/headroom/issues/2916))
([148d860](148d8605e2))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: JD Davis <mxjerrett@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-12 19:02:51 -05:00
Rod Boev
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>
2026-08-11 23:56:40 -05:00
Rod Boev
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.
2026-08-11 23:40:02 -05:00
Abhay Singh
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.
2026-08-11 09:10:38 -07:00
Tejas Chopra
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))
([13a310a](13a310a00d))
* **code:** add PHP support to CodeAwareCompressor
([#2423](https://github.com/headroomlabs-ai/headroom/issues/2423))
([6d5516d](6d5516dcb8))
* **compress:** accept config.frozen_message_count on /v1/compress
([#2718](https://github.com/headroomlabs-ai/headroom/issues/2718))
([2797099](2797099bec))
* **compress:** reach the lossless provider seam on the general path and
default /v1/compress to marker-free output
([#2691](https://github.com/headroomlabs-ai/headroom/issues/2691))
([f2c48e2](f2c48e26c6))
* **copilot:** proxy VS Code models transparently
([#2687](https://github.com/headroomlabs-ai/headroom/issues/2687))
([007446c](007446c73a))


### Bug Fixes

* **ccr:** stop persisting retrieval markers as original content
([#2694](https://github.com/headroomlabs-ai/headroom/issues/2694))
([#2703](https://github.com/headroomlabs-ai/headroom/issues/2703))
([3e348f3](3e348f327f))
* **ci:** restrict Codecov shard uploads
([#2745](https://github.com/headroomlabs-ai/headroom/issues/2745))
([3f2ca99](3f2ca99fe1))
* **compression:** honor qualified CCR names across integrations
([#2698](https://github.com/headroomlabs-ai/headroom/issues/2698))
([dcb674b](dcb674b5e4))
* **compress:** resolve the /v1/compress tokenizer per model, and
document the real contract
([#2743](https://github.com/headroomlabs-ai/headroom/issues/2743))
([6422a80](6422a80a58))
* **cost:** send litellm the total prompt so --budget stops seeing $0
([#2757](https://github.com/headroomlabs-ai/headroom/issues/2757))
([a033ac4](a033ac4176))
* **deps:** bump aiohttp and cryptography to clear the CVEs blocking
0.34.0
([#2753](https://github.com/headroomlabs-ai/headroom/issues/2753))
([0221e7f](0221e7f240))
* **kompress:** let orgs run Kompress on their own inference stack
([#2736](https://github.com/headroomlabs-ai/headroom/issues/2736))
([3d23d76](3d23d76248))
* **kompress:** load merged.pt for the v2 checkpoint instead of the
unmerged PEFT safetensors
([#2716](https://github.com/headroomlabs-ai/headroom/issues/2716))
([46da91b](46da91b2f1))
* **kompress:** reject artifacts that fail at run, and prefetch model
files at startup
([#2740](https://github.com/headroomlabs-ai/headroom/issues/2740))
([224578e](224578e80b))
* **learn:** filter ambient user-role scaffolding
([#2275](https://github.com/headroomlabs-ai/headroom/issues/2275))
([3eb0122](3eb0122068))
* **learn:** run project discovery off the event loop
([#2731](https://github.com/headroomlabs-ai/headroom/issues/2731))
([a70e5ff](a70e5ff78d))
* normalize /p/&lt;project&gt; prefix on WebSocket upgrades so the
Responses WS route is not rejected with 403
([#2379](https://github.com/headroomlabs-ai/headroom/issues/2379))
([789a4f3](789a4f3060))
* **providers:** give every model exactly one tokenizer
([#2761](https://github.com/headroomlabs-ai/headroom/issues/2761))
([cd92ed5](cd92ed52ff))
* **providers:** stop a shorter model family shadowing a longer one
([#2762](https://github.com/headroomlabs-ai/headroom/issues/2762))
([0cb72f4](0cb72f45b2))
* **providers:** stop pricing modern content blocks at zero
([#2760](https://github.com/headroomlabs-ai/headroom/issues/2760))
([06add9e](06add9e9d8))
* **proxy/cost:** mark estimated-basis budget records and add an
enforcement policy
([#2713](https://github.com/headroomlabs-ai/headroom/issues/2713))
([#2725](https://github.com/headroomlabs-ai/headroom/issues/2725))
([01df245](01df245252))
* **proxy/debug:** reconcile Kompress warmup state in /debug/warmup
([#2711](https://github.com/headroomlabs-ai/headroom/issues/2711))
([3a27c4d](3a27c4dacb))
* **proxy/openai:** run tool-description compaction on chat-completions
([#2741](https://github.com/headroomlabs-ai/headroom/issues/2741))
([f9db5b5](f9db5b5060))
* **proxy:** route Codex Live voice through a dedicated /v1/live
transport
([#2709](https://github.com/headroomlabs-ai/headroom/issues/2709))
([232fb49](232fb49c73))
* **proxy:** skip OpenAI tool_search deferral for Codex client
([#2729](https://github.com/headroomlabs-ai/headroom/issues/2729))
([56b3e4c](56b3e4c1b1))
* **proxy:** stop toggling headroom_retrieve in the Anthropic tools
array ([#2672](https://github.com/headroomlabs-ai/headroom/issues/2672))
([08fce29](08fce29b47))
* remove rtk and lean-ctx CLI context tools
([#2677](https://github.com/headroomlabs-ai/headroom/issues/2677))
([e0ce4b1](e0ce4b1d48))
* **router:** stop counting an image's base64 payload as suffix tokens
([#2778](https://github.com/headroomlabs-ai/headroom/issues/2778))
([f03cc6d](f03cc6d88b))
* **savings:** surface request growth the tok_saved clamp swallows
([#2708](https://github.com/headroomlabs-ai/headroom/issues/2708))
([184146b](184146b688))
* **stats:** report one "Tokens Saved" headline across every harness
([#2737](https://github.com/headroomlabs-ai/headroom/issues/2737))
([8262a4a](8262a4a321))
* **telemetry:** anonymous compression stats — no prompts, no data
([#2728](https://github.com/headroomlabs-ai/headroom/issues/2728))
([9cfb008](9cfb00838a))
* **telemetry:** stop mixing tokenizer scales in RequestOutcome, and fix
the overhead framing
([#2756](https://github.com/headroomlabs-ai/headroom/issues/2756))
([04e1517](04e1517ede))
* **tokenizers:** count HuggingFace chat templates, and resolve gpt-5 /
gateway-wrapped names
([#2758](https://github.com/headroomlabs-ai/headroom/issues/2758))
([0ed306b](0ed306b22b))
* **tokenizers:** resolve gpt-5 and mixed-case model names to the right
encoding
([#2776](https://github.com/headroomlabs-ai/headroom/issues/2776))
([fc4680b](fc4680b37a))
* **transforms:** stop ContentRouter recompressing headroom_retrieve
results
([#2654](https://github.com/headroomlabs-ai/headroom/issues/2654))
([677e097](677e09735a))
* **wrap/serena:** stop creating serena_config.yml, unbricking Serena on
fresh installs
([#2676](https://github.com/headroomlabs-ai/headroom/issues/2676))
([759209c](759209cff3))


### Code Refactoring

* **pricing:** make LiteLLM the source of truth, not the hardcoded table
([#2779](https://github.com/headroomlabs-ai/headroom/issues/2779))
([0e1d6bf](0e1d6bfa79))
* remove the dead headroom/prediction module
([#2692](https://github.com/headroomlabs-ai/headroom/issues/2692))
([b7a79ac](b7a79ac31a))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-04 19:39:34 -07:00
Tejas Chopra
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       a9a2fbd7  ci(docs): ... (#2746)
23:07:51 gh-pages 9cd8775c Deployed a9a2fbd7 with MkDocs <- 67s later
  ```

- The same workflow also carried the `deploy-vercel` job that failed 30
times on main without ever deploying (removed in #2746).

## Changes Made

- Removed the `validate-mkdocs` and `deploy-github-pages` jobs, and
`mkdocs.yml`.
- What remains is one workflow that **only validates** the Next.js build
on pull requests. Vercel owns deployment — duplicating that in Actions
is exactly what produced the dead `deploy-vercel` job.
- Dropped the `push` trigger entirely (nothing deploys from Actions now)
and the `wiki/**` / `mkdocs.yml` path filters.
- Renamed the workflow `Deploy Documentation` → `Validate Docs`, since
it no longer deploys anything. It isn't a required check, so the rename
is safe.
- Repointed `configuration.mdx`'s Filesystem Contract link from the
`wiki/` blob on GitHub to `/docs/filesystem-contract`, which already
existed.

## `wiki/` deliberately stays — please read this bit

I did **not** delete `wiki/`, even though the ask was to get rid of it.
~14 of its topics have no `docs/` equivalent, and one is significant:

```text
912L  wiki/cli.md               <- full CLI reference; docs/ has NO cli page
718L  wiki/macos-deployment.md
409L  wiki/compression.md
370L  wiki/transforms.md
345L  wiki/integration-guide.md
335L  wiki/api.md
292L  wiki/sdk.md
231L  wiki/learn.md
...
```

`docs/` mentions CLI commands across 27 pages but has no reference page
for them. Deleting `wiki/` today would drop 912 lines of CLI
documentation on the floor.

After this PR `wiki/` is **unpublished markdown**: nothing builds it,
nothing deploys it, so **it needs no syncing**. It's a migration
backlog, not a parallel site. That gets you the outcome you wanted — one
site, one tree to edit — without losing content.

## Type of Change

- [x] Code refactoring (no functional changes)

## Testing

- [x] Linting passes — workflow YAML parses and resolves to the intended
shape:

```text
name:      Validate Docs
jobs:      ['validate-nextjs']
triggers:  ['pull_request', 'workflow_dispatch']
pr paths:  ['docs/**', '.github/workflows/docs.yml']
```

Verified nothing else depends on the MkDocs pipeline: the only remaining
`mkdocs` references in the repo are `CHANGELOG.md` (history) and one
unrelated comment in `content_router.py:3329` ("Measured on
mkdocs.yml"). No `docs/` page links to a `wiki/` blob any more.

CI's `Validate Next.js build` is the real check that the surviving job
works.

## Two follow-ups this does NOT do

1. **Disable GitHub Pages and delete the `gh-pages` branch.** Pages is
currently enabled (`source: gh-pages`, status `building`) at
`https://headroomlabs-ai.github.io/headroom/`. After this PR nothing
updates it, so it freezes rather than breaks — and the Vercel `gh-pages`
build failure stops recurring because no further force-pushes happen.
Actually taking the site down and deleting the branch is a destructive,
outward-facing change; I'd rather do that as an explicit step than
bundle it here. Nothing in the repo links to it, so the only risk is
externally-indexed URLs 404ing.
2. **Migrate `wiki/cli.md` into `docs/` as a CLI reference page**, then
the smaller unique topics, then delete `wiki/`. That's content work
deserving its own review.
2026-08-04 16:42:49 -07:00
Tejas Chopra
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  13a310a0  failure
  30845343100  6422a80a  failure
  30810614546  007446c7  failure

per-job on 30874173333:
  failure  Deploy Vercel Docs
  success  Deploy GitHub Pages     <- same run
  skipped  Validate mkdocs build
  skipped  Validate Next.js build
```

`deploy-github-pages` succeeded in those same runs, so this job
contributed nothing but a red X on every merge to main.

The Next.js site is published by **Vercel's own Git integration**, which
is what has actually been deploying it. Removing this job leaves exactly
one deploy path per site: Pages via mkdocs here, Vercel via its Git
integration.

## Why this PR previously said the opposite

I opened this as "drop the redundant Vercel job", then converted it to
draft and posted a correction saying the deletion was **wrong** —
because at that point the Vercel site had been stale since mid-June and
this looked like the only deploy path. That correction was itself based
on a wrong assumption: with no `VERCEL_TOKEN` configured, this job could
never have deployed anything. It wasn't the deploy path; it was a job
that had always failed. The site was stale because *nothing* was
publishing it until the Git integration was connected.

So: original intent right, first correction wrong, and the evidence
above is what settles it. Recording that rather than quietly re-flipping
the description.

## Type of Change

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

## Changes Made

- Push filter: `docs/**` → `wiki/**` (what mkdocs actually reads).
- Removed the `deploy-vercel` job.
- Replaced it with a comment recording *why* there is no Vercel job
here, so nobody re-adds one.

## Testing

- [x] Linting passes — workflow YAML parses, and the resulting shape is
what's intended:

```text
jobs:       ['validate-mkdocs', 'validate-nextjs', 'deploy-github-pages']
push paths: ['wiki/**', 'mkdocs.yml', '.github/workflows/docs.yml']
pr paths:   ['docs/**', 'wiki/**', 'mkdocs.yml', '.github/workflows/docs.yml']
```

CI is the real check for a workflow change. The observable proof after
merge is that the next push to `main` no longer reports a failing
`Deploy Vercel Docs`, and that a `wiki/**`-only change triggers a Pages
deploy (it currently does not).

## Real Behavior Proof

- **Environment:** GitHub Actions on `headroomlabs-ai/headroom`, branch
`main`.
- **Exact command / steps:** `gh run list --workflow "Deploy
Documentation" --branch main`; `gh api .../actions/secrets`; `gh run
view <id> --json jobs`.
- **Observed result:** 30 consecutive `Deploy Documentation` failures on
main attributable solely to `Deploy Vercel Docs`; no Vercel secrets
configured at repo or org level; `Deploy GitHub Pages` green throughout.

## What this does NOT fix

This is unrelated to the failing **`Vercel`** check currently showing on
~18 open PRs. That one comes from the Vercel GitHub App with
`Authorization required to deploy.` — Vercel asking each PR author to
authorize its app — and is a Vercel-side setting, not a workflow file.
Two different mechanisms that both say "Vercel":

| | mechanism | where it fails | fixed here? |
|---|---|---|---|
| `Deploy Vercel Docs` | Actions job in this file | pushes to `main` |
**yes** |
| `Vercel` / `Vercel Preview Comments` | Vercel GitHub App | contributor
PRs | no — dashboard setting |

For the record: `Vercel` is not a required status check (`template`,
`label`, `merge-conflicts`, `no-manual-changelog`, `Secret scan
(gitleaks)` are), so it has never actually blocked a merge.
2026-08-04 16:06:44 -07:00
Raúl
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.
2026-08-03 14:20:17 -07:00
Tejas Chopra
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.
2026-07-30 22:59:41 -07:00
Tejas Chopra
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))
([7dc9a97](7dc9a978ca))
* **metrics:** record per-extension token savings
([#2371](https://github.com/headroomlabs-ai/headroom/issues/2371))
([02eb90f](02eb90f243))
* **opencode:** ship the transport plugin in pip installs
([#2601](https://github.com/headroomlabs-ai/headroom/issues/2601))
([f54f04f](f54f04f5bf))
* **opencode:** support Copilot subscription backend for headroom models
([#2441](https://github.com/headroomlabs-ai/headroom/issues/2441))
([#2445](https://github.com/headroomlabs-ai/headroom/issues/2445))
([9089e7f](9089e7f7d3))
* **proxy/hooks:** run fold-only (stream-safe) turn hooks on streaming
OpenAI chat
([#2549](https://github.com/headroomlabs-ai/headroom/issues/2549))
([a6d4921](a6d4921e82))
* **proxy/savings:** aggregate tool-schema savings into Metrics + all
reporting sinks
([#2546](https://github.com/headroomlabs-ai/headroom/issues/2546))
([9f1ffef](9f1ffefe83))
* **proxy:** label GitHub Copilot traffic as "copilot" in the outcome…
([#2377](https://github.com/headroomlabs-ai/headroom/issues/2377))
([d7a8cdb](d7a8cdbee1))
* **proxy:** make /v1/compress usable as a gateway/Kong sidecar
([#2458](https://github.com/headroomlabs-ai/headroom/issues/2458))
([1329ed7](1329ed7f1a))
* **proxy:** model-aware cold-prefix hook — reasoning compaction
(Kimi/GLM) + cold recompaction (CC)
([#2555](https://github.com/headroomlabs-ai/headroom/issues/2555))
([cb8f4b6](cb8f4b6436))
* **proxy:** route selected external compressors through the content
router
([#2388](https://github.com/headroomlabs-ai/headroom/issues/2388))
([e3c7964](e3c7964038))
* **proxy:** select built-in compressors via --compressor + registry
inventory
([#2373](https://github.com/headroomlabs-ai/headroom/issues/2373))
([56c7d4a](56c7d4a59e))
* **rust:** add structured prose offload plumbing
([#334](https://github.com/headroomlabs-ai/headroom/issues/334))
([#2378](https://github.com/headroomlabs-ai/headroom/issues/2378))
([9e07785](9e0778553f))
* **rust:** port CodeCompressor AST compressor to Rust (parity-only)
([#1154](https://github.com/headroomlabs-ai/headroom/issues/1154))
([e530de5](e530de5ad2))
* **rust:** port Kompress ML prose compressor to Rust (parity-only)
([#1153](https://github.com/headroomlabs-ai/headroom/issues/1153))
([83e27e5](83e27e5036))
* **telemetry:** record provider cache read/write/uncached tokens per
request
([#2450](https://github.com/headroomlabs-ai/headroom/issues/2450))
([bec4cce](bec4cce8a9))
* **transforms:** add compressed signal + dispatch code_aware/html/diff
via registry
([#2400](https://github.com/headroomlabs-ai/headroom/issues/2400))
([7ebda67](7ebda67ef6))
* **transforms:** add pluggable compressor registry +
headroom.compressor entry point
([#2370](https://github.com/headroomlabs-ai/headroom/issues/2370))
([a02073e](a02073e332))
* **transforms:** dispatch kompress/text via the compressor registry +
forward question
([#2411](https://github.com/headroomlabs-ai/headroom/issues/2411))
([446ec26](446ec26003))
* **transforms:** dispatch smart_crusher via the compressor registry
(defer kompress/text ML boundary)
([#2404](https://github.com/headroomlabs-ai/headroom/issues/2404))
([7c7bf43](7c7bf43057))
* **transforms:** make built-in compressors real Compressor
implementations (adapters)
([#2391](https://github.com/headroomlabs-ai/headroom/issues/2391))
([981616c](981616c60e))
* **wrap:** boost Serena — symbol-first guidance, wrap-time pre-index,
repo-language scoping
([#2425](https://github.com/headroomlabs-ai/headroom/issues/2425))
([fd0e1a8](fd0e1a8afe))
* **wrap:** default code-memory to Serena (dashboard browser off) behind
unified --code-memory
([#2413](https://github.com/headroomlabs-ai/headroom/issues/2413))
([6e4425a](6e4425a6bd))
* **wrap:** reduce-at-source — SAFE quiet-CLI env defaults for the
launched agent
([#2548](https://github.com/headroomlabs-ai/headroom/issues/2548))
([c990cfb](c990cfb803))


### Bug Fixes

* **backends/litellm:** guard None completion_tokens in usage mapping
([#2322](https://github.com/headroomlabs-ai/headroom/issues/2322))
([44a174f](44a174fef4))
* **backends:** don't crash the OpenAI-&gt;Anthropic converter on empty
choices
([#2484](https://github.com/headroomlabs-ai/headroom/issues/2484))
([43a7b57](43a7b578a1))
* **cache:** preserve cache_control ttl when re-anchoring a breakpoint
([#2651](https://github.com/headroomlabs-ai/headroom/issues/2651))
([e0d2cd0](e0d2cd0c5a))
* **cache:** preserve client cache_control ttl when consolidating
breakpoints
([#2382](https://github.com/headroomlabs-ai/headroom/issues/2382))
([8906d3a](8906d3a676))
* **ccr:** guard empty/malformed OpenAI choices in
_extract_assistant_message
([#2389](https://github.com/headroomlabs-ai/headroom/issues/2389))
([89319fb](89319fbcad))
* **ccr:** sliding idle-window TTL with max-lifetime ceiling in the Rust
core backends
([#2604](https://github.com/headroomlabs-ai/headroom/issues/2604))
([#2631](https://github.com/headroomlabs-ai/headroom/issues/2631))
([e825588](e825588bfb))
* **ci:** align Ruff tooling versions
([#2406](https://github.com/headroomlabs-ai/headroom/issues/2406))
([2bb14d1](2bb14d1ab2))
* **cli:** warn when Headroom proxy URL leaks into the shell after
unwrap claude
([#2238](https://github.com/headroomlabs-ai/headroom/issues/2238))
([#2571](https://github.com/headroomlabs-ai/headroom/issues/2571))
([904bc67](904bc675b3))
* **codex:** detect keyring-backed ChatGPT auth
([#2478](https://github.com/headroomlabs-ai/headroom/issues/2478))
([46293f4](46293f4daf))
* **compression:** report source-line span in CCR compression marker
([#2597](https://github.com/headroomlabs-ai/headroom/issues/2597))
([18e1c3c](18e1c3c9ba))
* **copilot:** derive GHE credential host from API URL
([#800](https://github.com/headroomlabs-ai/headroom/issues/800))
([#2511](https://github.com/headroomlabs-ai/headroom/issues/2511))
([4a8157f](4a8157fa0a))
* **copilot:** normalize subscription API routing
([#2441](https://github.com/headroomlabs-ai/headroom/issues/2441))
([#2455](https://github.com/headroomlabs-ai/headroom/issues/2455))
([2eca5ee](2eca5ee114))
* **copilot:** preserve /v1 for the Anthropic /v1/messages endpoint
([#2409](https://github.com/headroomlabs-ai/headroom/issues/2409))
([#2414](https://github.com/headroomlabs-ai/headroom/issues/2414))
([c400f90](c400f90810))
* **deps:** bump mcp to 1.28.1 to clear 3 high-severity CVEs
([#2348](https://github.com/headroomlabs-ai/headroom/issues/2348))
([a90be94](a90be94e32))
* **grok:** preserve business-seat auth while routing only inference
([#2514](https://github.com/headroomlabs-ai/headroom/issues/2514))
([e4076bb](e4076bbe99))
* **image:** reuse image models instead of rebuilding them per request
([#2513](https://github.com/headroomlabs-ai/headroom/issues/2513))
([#2536](https://github.com/headroomlabs-ai/headroom/issues/2536))
([2a63ec7](2a63ec70b6))
* **install:** carry upstream-routing env overrides into supervised
deployments
([#2429](https://github.com/headroomlabs-ai/headroom/issues/2429))
([170b04a](170b04a74d))
* **install:** default to cache mode, matching `headroom proxy`
([#1893](https://github.com/headroomlabs-ai/headroom/issues/1893)
follow-up)
([#2563](https://github.com/headroomlabs-ai/headroom/issues/2563))
([b121223](b121223ec9))
* **install:** migrate deployments off the retired chopratejas image
repo ([#2427](https://github.com/headroomlabs-ai/headroom/issues/2427))
([17ff13c](17ff13ccbe))
* **install:** use CREATE_NO_WINDOW instead of DETACHED_PROCESS on
Windows
([#2527](https://github.com/headroomlabs-ai/headroom/issues/2527))
([045f3df](045f3dfe6f))
* **kompress:** raise the default execution-slot wait
([#2456](https://github.com/headroomlabs-ai/headroom/issues/2456))
([5bd2266](5bd2266f16))
* **learn:** detect the active OpenCode database
([#2587](https://github.com/headroomlabs-ai/headroom/issues/2587))
([f74d874](f74d874777))
* **learn:** keep traceback tail in tool-error digest preview
([#2596](https://github.com/headroomlabs-ai/headroom/issues/2596))
([85e8699](85e8699451))
* **learn:** treat unreadable candidate paths as absent in project
decode
([#2446](https://github.com/headroomlabs-ai/headroom/issues/2446))
([a09ba6c](a09ba6c087))
* **mcp:** pin mcp dependency to &lt;2.0.0 to prevent server startup
crash ([#2642](https://github.com/headroomlabs-ai/headroom/issues/2642))
([b3f016b](b3f016b866))
* **proxy/cost:** count Gemini thinking tokens in output usage
([#2639](https://github.com/headroomlabs-ai/headroom/issues/2639))
([22b707f](22b707fd31))
* **proxy/cost:** record each request's savings exactly once (drop 3
double-counts)
([#2545](https://github.com/headroomlabs-ai/headroom/issues/2545))
([0845b26](0845b26ee6))
* **proxy/cost:** warn once per model when pricing lookup fails
([#2504](https://github.com/headroomlabs-ai/headroom/issues/2504))
([#2535](https://github.com/headroomlabs-ai/headroom/issues/2535))
([fa47637](fa4763761b))
* **proxy/gemini:** None-guard token counts from usageMetadata
([#2347](https://github.com/headroomlabs-ai/headroom/issues/2347))
([f64aac9](f64aac9733))
* **proxy/gemini:** tolerate malformed parts on the compression path
([#2486](https://github.com/headroomlabs-ai/headroom/issues/2486))
([07cf547](07cf547607))
* **proxy/metrics:** move the savings-ledger append off the event loop
([#2439](https://github.com/headroomlabs-ai/headroom/issues/2439))
([4aac068](4aac068814))
* **proxy/openai:** cache under looked-up messages
([#2420](https://github.com/headroomlabs-ai/headroom/issues/2420))
([7052d52](7052d52dcb))
* **proxy/openai:** don't record Codex WS savings without input
accounting
([#2493](https://github.com/headroomlabs-ai/headroom/issues/2493))
([2195ba7](2195ba7d91))
* **proxy/openai:** feed chat/completions traffic into the traffic
learner
([#2333](https://github.com/headroomlabs-ai/headroom/issues/2333))
([6cdfd3f](6cdfd3f64d))
* **proxy/openai:** None-guard usage token counts on the chat path
([#2431](https://github.com/headroomlabs-ai/headroom/issues/2431))
([313c290](313c290df9))
* **proxy/openai:** replay incremental events in buffered Responses SSE
([#2410](https://github.com/headroomlabs-ai/headroom/issues/2410))
([#2415](https://github.com/headroomlabs-ai/headroom/issues/2415))
([0cbc0e8](0cbc0e8e54))
* **proxy/output-shaping:** tolerate a non-string system block text in
steering
([#2435](https://github.com/headroomlabs-ai/headroom/issues/2435))
([3e97671](3e976712e7))
* **proxy/perf:** count turn-hook message folds in token accounting
([#2520](https://github.com/headroomlabs-ai/headroom/issues/2520))
([c371d5a](c371d5ad60))
* **proxy/perf:** tokenizer-consistent token accounting + surface
tool-schema savings
([#2542](https://github.com/headroomlabs-ai/headroom/issues/2542))
([1cc53c9](1cc53c9c92))
* **proxy/streaming:** tolerate malformed content in _response_to_sse
([#2481](https://github.com/headroomlabs-ai/headroom/issues/2481))
([77b26c0](77b26c093c))
* **proxy:** keep buffered CCR streams alive
([#2479](https://github.com/headroomlabs-ai/headroom/issues/2479))
([a2e42fb](a2e42fb877))
* **proxy:** keep core tools and the client's ToolSearch resident for
PascalCase clients
([#2647](https://github.com/headroomlabs-ai/headroom/issues/2647))
([1d29738](1d29738818))
* **proxy:** offload OpenAI and Gemini tokenizer counting off the event
loop ([#2498](https://github.com/headroomlabs-ai/headroom/issues/2498))
([806d2e4](806d2e468a))
* **proxy:** promote Kompress health after runtime load
([#2402](https://github.com/headroomlabs-ai/headroom/issues/2402))
([54526bc](54526bc858))
* **proxy:** reassemble server_tool_use.input from streamed partial_json
([#2449](https://github.com/headroomlabs-ai/headroom/issues/2449))
([8c8fae0](8c8fae0d0b))
* **proxy:** report deferred Kompress status and promote health from
cache ([#2564](https://github.com/headroomlabs-ai/headroom/issues/2564))
([d50cfab](d50cfabedc))
* **proxy:** skip max_tokens rename for backend-routed openai chat
([#2401](https://github.com/headroomlabs-ai/headroom/issues/2401))
([d6a1af4](d6a1af40d5))
* **release:** publish Windows wheel + sdist (disable PyPI attestations,
[#112](https://github.com/headroomlabs-ai/headroom/issues/112))
([#2405](https://github.com/headroomlabs-ai/headroom/issues/2405))
([f9cbdd6](f9cbdd6e39))
* **release:** sync generated version metadata on the release branch
([#2659](https://github.com/headroomlabs-ai/headroom/issues/2659))
([5383c6b](5383c6bf2f))
* **rust:** port CJK-aware relevance-query matching to CodeCompressor
([#2634](https://github.com/headroomlabs-ai/headroom/issues/2634))
([e86c639](e86c6390ce))
* **security:** exclude compromised ast-grep-cli 0.44.1 (supply-chain
trojan)
([#2342](https://github.com/headroomlabs-ai/headroom/issues/2342))
([494fb5a](494fb5a60e))
* **tokenizers:** price Claude against a real BPE (tiktoken o200k) not a
char estimate
([#2543](https://github.com/headroomlabs-ai/headroom/issues/2543))
([285176b](285176be54))
* **transforms/cross-turn-dedup:** don't renumber-fold zero-padded line
prefixes
([#2369](https://github.com/headroomlabs-ai/headroom/issues/2369))
([f4070c4](f4070c44cb))
* **transforms/kompress-remote:** keep compress fail-open on malformed
200 ([#2320](https://github.com/headroomlabs-ai/headroom/issues/2320))
([b759990](b75999017f))
* **wrap:** emit bare dotted keys for Codex --config overrides
([#2383](https://github.com/headroomlabs-ai/headroom/issues/2383))
([f57e959](f57e959a50))
* **wrap:** make RTK opt-in (off by default) across wrap subcommands
([#2344](https://github.com/headroomlabs-ai/headroom/issues/2344))
([44136ed](44136ed042))
* **wrap:** skip Serena project setup outside real project roots
([#2574](https://github.com/headroomlabs-ai/headroom/issues/2574))
([0994ea0](0994ea04c8))
* **wrap:** stop same-port persistent routing during claude unwrap
([#2340](https://github.com/headroomlabs-ai/headroom/issues/2340))
([#2350](https://github.com/headroomlabs-ai/headroom/issues/2350))
([cf5fa64](cf5fa644b6))


### Performance Improvements

* **content_router:** dedupe content detection
([#2419](https://github.com/headroomlabs-ai/headroom/issues/2419))
([9b016f2](9b016f2b64))


### Dependencies

* bump the cargo-minor-patch group with 10 updates
([#2284](https://github.com/headroomlabs-ai/headroom/issues/2284))
([3266ed7](3266ed7641))
* bump the npm-minor-patch group across 3 directories with 7 updates
([#2276](https://github.com/headroomlabs-ai/headroom/issues/2276))
([961866b](961866ba7c))


### Code Refactoring

* **transforms:** dispatch simple built-in strategies via the compressor
registry
([#2399](https://github.com/headroomlabs-ai/headroom/issues/2399))
([fc9c63f](fc9c63f18c))
* **wrap:** retire tokensave; Serena is the code-memory MCP
([#2499](https://github.com/headroomlabs-ai/headroom/issues/2499))
([5d23a0a](5d23a0aec2))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-29 15:54:23 -07:00
Tejas Chopra
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 `f9cbdd6e` / #2405), and
`create-release` was skipped because it depends on it. That is why the
release looked like it produced nothing.

**Separate, approaching blocker — not addressed here.** PyPI is at
**9.69 GB of its 10 GB project cap (96.9%)**, leaving ~305 MB against
~68 MB per release, so roughly 4 more releases fit. The `0.21.x` series
alone holds **6.58 GB across 31 releases**, from the old
every-push-is-a-release era; pruning it would reclaim two thirds of the
quota. Worth a separate issue.

**`.releasemetadata` is written but never read** by anything outside
`version-sync.py` and its test. It is kept in sync here for internal
consistency, but it may be a deletion candidate.
2026-07-29 15:12:04 -07:00
Tejas Chopra
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.
2026-07-27 08:04:53 -07:00
Tejas Chopra
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.
2026-07-27 07:34:00 -07:00
gglucass
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>
2026-07-27 06:40:43 -07:00
Tejas Chopra
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.
2026-07-26 11:00:14 -07:00
Tejas Chopra
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>
2026-07-18 20:56:31 -07:00
Floze
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.
2026-07-18 20:55:20 -07:00
Tejas Chopra
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.
2026-07-16 14:28:57 -07:00
Ashish
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 `c71cc0cb` once #1187 lands. Please
review/merge #1187 first.

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

- **`CompressionOnlyRunner.evaluate_dataset_recall(suite)`**: for each
QA case, compress the supporting `context` via the production routing
path (`ContentRouter`) and check the `ground_truth` answer survives
(`compute_information_recall`). Counts only **probeable** cases — answer
literally present in the context and non-trivial (skips `yes/no`,
too-short) — so the aggregate is meaningful rather than inflated by
un-measurable cases.
- **`.github/workflows/eval.yml`**: a non-blocking step in the existing
`weekly-suite` job (schedule/manual only) drives it with
`load_hotpotqa(n=50)`. Defensive: a dataset download or model failure
emits `:⚠️:` and `|| true`, never failing the job.
- **Hermetic unit test** (`tests/test_dataset_recall_runner.py`):
exercises the method with synthetic JSON-array contexts (SmartCrusher /
Rust — no model, no network), so it runs in the standard `[dev]` shard.

### Scope notes

- **Prose path only.** BFCL / tool-schema integrity is already covered
by the existing `evaluate_tool_schema_compaction` eval (which runs in
the PR smoke-test), so this targets the previously-uncovered prose
recall path. NQ is an easy further extension using the same method +
`load_natural_questions`.
- **Why weekly, not per-PR.** Real datasets need a network download +
the ModernBERT model. The `weekly-suite` job already installs `[all]`
and genuinely runs every Monday (verified: 5 consecutive successful
scheduled runs), so it's the correct home — keeping PR CI fast and
hermetic.

## 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
$ HF_HUB_OFFLINE=1 python -m pytest tests/test_dataset_recall_runner.py -q
..                                                                       [100%]
2 passed in 0.20s
```

## Real Behavior Proof

- Environment: local checkout of `feat/weekly-dataset-recall`, `pip
install -e ".[dev]"`, `HF_HUB_OFFLINE=1` (proves the unit tests need no
model/network)
- Exact command / steps: `HF_HUB_OFFLINE=1 python -m pytest
tests/test_dataset_recall_runner.py -q` -> `6 passed in 0.36s`; coverage
JSON confirms the runner's per-case exception handler and both
`warm_kompress_model` outcomes are exercised
- Observed result: with a synthetic suite of 3 cases (one probeable
answer in an error row, one trivial `yes`, one absent answer),
`evaluate_dataset_recall` counts only the 1 probeable case (`passed=1`,
`accuracy_rate=1.0`, `benchmark="dataset_recall:synthetic"`); a
monkeypatched compressor crash records the error and counts the case
failed instead of aborting; the new weekly-suite YAML step parses via
`yaml.safe_load` and sits under the `schedule || workflow_dispatch`
guard
- Not tested: the live HotpotQA download + ModernBERT compression --
exercised only by the weekly job (or `workflow_dispatch`), by design

## 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
- [ ] I have updated the CHANGELOG.md if applicable

## Additional Notes

- CHANGELOG/version intentionally untouched: repo uses
**release-please**.
- The weekly job can be triggered on demand via **workflow_dispatch** to
see the HotpotQA recall numbers without waiting for Monday.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: JD Davis <mxjerrett@gmail.com>
2026-07-15 21:40:55 +00:00
ninosat00
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>
2026-07-14 16:07:34 -04:00
Ashish Patel
7ab83c5107
fix(router): stop protecting passing build/test output as error traces (#1740)
## Description

![Error output protection false-positive
fix](https://raw.githubusercontent.com/ashishpatel26/headroom/fix/1696-error-protection-false-positive/.github/pr-images/issue-1696-error-protection-fix.svg)

`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.
2026-07-14 13:25:49 -04:00
Parideboy
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` (9fbd47ba).
- Exact command / steps: `cargo check -p headroom-core` after the
feature switch; inspected the `Cargo.lock` diff; rebuilt and ran `python
-c "import headroom; from headroom._core import detect_content_type;
print(detect_content_type('hello world'))"`; ran the ort-pin test suite
with monkeypatched `linux`/`darwin` platforms.
- Observed result: build succeeds with `ort-load-dynamic`; the lockfile
shows `ort-sys` no longer pulls the binary-download machinery
(`hmac-sha256`, `lzma-rust2`, `ureq` removed), confirming the
statically-linked prebuilt ORT is gone; import + content detection works
with `ORT_DYLIB_PATH` auto-pinned to the pip onnxruntime library; all 8
pin tests pass including the new Linux/macOS branches.
- Not tested: actual pre-AVX2 x86-64 hardware (none available — the fix
removes AVX2 code from the import path by construction, and the issue
reporters on #1278 can verify); Linux/macOS wheel runtime behavior
beyond CI's ubuntu/macOS wheel-build jobs; embedding quality/performance
under a pip-provided ORT version differing from the previously vendored
one.

## Review Readiness

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-07-14 13:25:41 -04:00
Gaurav Yadav
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.
2026-07-14 13:25:18 -04:00
JD Davis
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`
2026-07-14 11:43:23 -04:00
Tejas Chopra
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.
2026-07-13 23:50:26 -04:00
Tejas Chopra
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.
2026-07-13 13:47:59 -04:00
JD Davis
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 `9bacf481`.
- **Exact simulator command / steps:**  
  - `cargo run -p headroom-simulators -- --listen 127.0.0.1:8789`  
- Point Headroom proxy upstream at `http://127.0.0.1:8789` for local
deterministic provider responses.
- Use optional `--config path/to/simulator.json` to bind exact request
fixtures.
- **Observed simulator result:**  
  - OpenAI chat default returns `chat.completion` shape.  
  - OpenAI Responses stream returns named SSE events.  
  - Vertex raw predict returns Anthropic message shape.  
- Bedrock stream can return binary `application/vnd.amazon.eventstream`
bytes.
  - Configured stubs override bottled defaults.  
- **Observed Magika result:**  
- Direct Rust `headroom-core` tests pass with `ORT_DYLIB_PATH` unset.
- Magika discovers the installed pip `onnxruntime.dll`, loads it via
`ort::init_from`, and only falls back if no safe runtime is available.
- **Not tested:**  
- No live provider calls; simulator behavior is intentionally offline
and deterministic.

## 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  
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)  
N/A

## Additional Notes  
No CHANGELOG entry was added because this introduces a developer/CI
simulator crate plus a Windows direct-Rust Magika runtime fix, without
changing shipped Python package behavior. The simulator intentionally
does not include a lightweight fallback LLM in this slice; unbound
inputs receive deterministic bottled responses so tests stay
reproducible and offline.
2026-07-11 09:41:49 -07:00
sgfdhgfnjgmhgmnfd657-sys
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>
2026-07-10 23:05:03 -05:00
JD Davis
98ff203f98
ci: allow PyPI deps during CPU torch install (#1930)
## Description

Fixes the CI failure exposed on the `8527b910` push run:

- Run:
https://github.com/headroomlabs-ai/headroom/actions/runs/29063619700
- Failed job: `test (3)` / job `86271743483`
- Failed step: `Install (CPU torch + prebuilt wheel + dev deps, no cargo
rebuild)`

The failing command used the PyTorch CPU index as the only package
index:

```bash
pip install torch --index-url https://download.pytorch.org/whl/cpu
```

That index does not provide transitive dependencies such as
`typing-extensions`, so pip backtracked across torch CPU wheels and
failed before tests ran. This PR keeps the PyTorch CPU index primary for
`torch` while allowing PyPI for dependencies.

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

- Adds `--extra-index-url https://pypi.org/simple` to each CI `pip
install torch --index-url https://download.pytorch.org/whl/cpu` command.
- Leaves the CPU torch index as the primary index so CI still installs
CPU torch wheels.

## Testing

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

### Test Output

```text
git diff --check
# passed

rg -n "pip install torch" .github/workflows/ci.yml
# all four torch install commands now include --extra-index-url https://pypi.org/simple
```

## Real Behavior Proof

- Environment: Local worktree `C:\git\headroom-ci-fix`, branch
`jd/fix-ci-torch-install-index`.
- Exact command / steps: Inspected failed GitHub Actions log for run
`29063619700` job `86271743483`, then updated matching torch install
commands in `.github/workflows/ci.yml`.
- Observed result: The workflow no longer uses the PyTorch CPU index as
the only index for torch installs; PyPI is available for transitive
dependencies such as `typing-extensions`.
- Not tested: Full CI run locally; GitHub Actions is the authoritative
verification for the affected install path.

## Review Readiness

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

## Checklist

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

## Screenshots (if applicable)

N/A.

## Additional Notes

This is a fix-forward PR. Reverting `8527b910` would reintroduce the
Ruff failure it fixed; the failed job did not reach tests and failed
only during dependency installation.
2026-07-09 19:36:26 -07:00
JerrettDavis
2d418335a1 ci: preserve merge labels while state is unknown 2026-07-09 19:51:41 -05:00
JerrettDavis
595b709a5b ci: keep ready label off changes-requested PRs 2026-07-09 19:49:55 -05:00
Tejas Chopra
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)
2026-07-09 16:55:51 -05:00
Mohammad Hussian
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>
2026-07-09 16:53:55 -05:00
dependabot[bot]
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 &amp; 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 &quot;Cache save failed&quot; 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="55cc834586"><code>55cc834</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1768">#1768</a>
from jasongin/readonly-cache</li>
<li><a
href="d8cd72f230"><code>d8cd72f</code></a>
Bump <code>@​actions/cache</code> to v6.1.0 - handle cache write error
due to RO token</li>
<li><a
href="2c8a9bd745"><code>2c8a9bd</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1760">#1760</a>
from actions/samirat/esm_migration_and_package_update</li>
<li><a
href="e9b91fdc3f"><code>e9b91fd</code></a>
Prettier fixes</li>
<li><a
href="e4884b8ff7"><code>e4884b8</code></a>
Rebuild dist</li>
<li><a
href="10baf0191a"><code>10baf01</code></a>
Fixed licenses</li>
<li><a
href="e39b386c90"><code>e39b386</code></a>
Fix test mock return order</li>
<li><a
href="b692820337"><code>b692820</code></a>
PR feedback</li>
<li><a
href="60749128a4"><code>6074912</code></a>
Rebuild dist bundles as ESM to match type:module</li>
<li><a
href="5a912e8b4a"><code>5a912e8</code></a>
Fix lint and jest issues</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/cache/compare/v5...v6">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 13:32:20 -05:00
dependabot[bot]
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="9c091bb21b"><code>9c091bb</code></a>
update error wording (<a
href="https://redirect.github.com/actions/checkout/issues/2467">#2467</a>)</li>
<li><a
href="1044a6dea9"><code>1044a6d</code></a>
getting ready for checkout v7 release (<a
href="https://redirect.github.com/actions/checkout/issues/2464">#2464</a>)</li>
<li><a
href="f0282184c7"><code>f028218</code></a>
Bump the minor-npm-dependencies group across 1 directory with 3 updates
(<a
href="https://redirect.github.com/actions/checkout/issues/2462">#2462</a>)</li>
<li><a
href="d914b262ff"><code>d914b26</code></a>
upgrade module to esm and update dependencies (<a
href="https://redirect.github.com/actions/checkout/issues/2463">#2463</a>)</li>
<li><a
href="537c7ef99c"><code>537c7ef</code></a>
Bump <code>@​actions/core</code> and <code>@​actions/tool-cache</code>
and Remove uuid (<a
href="https://redirect.github.com/actions/checkout/issues/2459">#2459</a>)</li>
<li><a
href="130a169078"><code>130a169</code></a>
Bump js-yaml from 4.1.0 to 4.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2461">#2461</a>)</li>
<li><a
href="7d09575332"><code>7d09575</code></a>
Bump flatted from 3.3.1 to 3.4.2 (<a
href="https://redirect.github.com/actions/checkout/issues/2460">#2460</a>)</li>
<li><a
href="0f9f3aa320"><code>0f9f3aa</code></a>
Bump actions/publish-immutable-action (<a
href="https://redirect.github.com/actions/checkout/issues/2458">#2458</a>)</li>
<li><a
href="f9e715a95f"><code>f9e715a</code></a>
block checking out fork pr for pull_request_target and workflow_run (<a
href="https://redirect.github.com/actions/checkout/issues/2454">#2454</a>)</li>
<li><a
href="df4cb1c069"><code>df4cb1c</code></a>
Update changelog for v6.0.3 (<a
href="https://redirect.github.com/actions/checkout/issues/2446">#2446</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/checkout/compare/v4...v7">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 11:09:14 -05:00
Tejas Chopra
807e5e3e6a
ci(release-please): use a PAT so releases trigger the publish workflows (#1920)
## Description

Makes the release pipeline hands-off by fixing the token release-please
uses.

### Why the current setup silently breaks publishing
release-please authenticates with `secrets.GITHUB_TOKEN`. **A
release/tag created by `GITHUB_TOKEN` does not emit events that trigger
other workflows** — this is GitHub's built-in recursion guard. So
`release.yml` (PyPI + npm) and `docker.yml`, which both fire on
`release: published`, **never ran off a bot-created release**. The
result: releases had to be cut by hand (`gh release create`, which runs
as a real user and *does* trigger them), and pip / npm / Docker drifted
out of sync (pip 0.30 vs Docker 0.27).

Proof: creating v0.31.0 manually (my user token) immediately kicked off
both `Release: v0.31.0` and `Docker: v0.31.0`; a `GITHUB_TOKEN`-created
release would not have.

### Change
Use `RELEASE_PLEASE_TOKEN` (a fine-grained PAT with `contents: write` +
`pull-requests: write`, treated by GitHub as a real user):
```yaml
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
```
- The release the bot creates now **does** trigger `release.yml` /
`docker.yml` → PyPI + npm + Docker publish automatically on merge of the
release PR.
- The PAT can also tag past branch/tag protection.
- Falls back to `GITHUB_TOKEN` if the secret is ever unset — the release
PR still opens; it just won't trigger downstream publishes (i.e. no
worse than today).

The `RELEASE_PLEASE_TOKEN` secret is already configured in repo
settings.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `.github/workflows/release-please.yml`: swap `token: ${{
secrets.GITHUB_TOKEN }}` for `${{ secrets.RELEASE_PLEASE_TOKEN ||
secrets.GITHUB_TOKEN }}`, with a comment explaining the recursion-guard
reason.

## Testing

- [x] Manual testing performed (YAML validated; token expression
resolves)

### Test Output

```text
$ python -c "import yaml; ... token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}"
parse OK
```

## Real Behavior Proof

- Environment: local macOS; CI workflow YAML change only.
- Exact command / steps: changed the `token:` input on the
`release-please-action` step to the PAT (with GITHUB_TOKEN fallback);
validated the workflow YAML parses and the token expression is correct.
- Observed result: the workflow now authenticates release-please as a
real user via `RELEASE_PLEASE_TOKEN`, so releases it creates will emit
`release: published` and trigger `release.yml` + `docker.yml`. Verified
out-of-band that a user-token release does trigger those two workflows
(v0.31.0), whereas the bot token does not.
- Not tested: a full bot-driven release cycle end-to-end (only
observable when the next release PR merges with this token in place);
this PR is the enabling change for that.

## 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
2026-07-09 09:07:19 -07:00