Skip to content

Promote main to release - #2425

Merged
copse-release-bot[bot] merged 10 commits into
releasefrom
main
Sep 6, 2026
Merged

copse-release-bot[bot] merged 10 commits into
releasefrom
main

Conversation

@copse-release-bot

Copy link
Copy Markdown
Contributor

Daily automated promotion of main to the release-ready release branch.

The repository ruleset requires the full CI Passed gate. This PR will
merge automatically when that gate is green; failures leave it open
until the underlying issue is fixed on main.

jonathanKingston and others added 10 commits September 6, 2026 01:20
## Summary

- push the implicit current branch to `origin` inside the confirmed
Create PR operation
- require committed changes, preserve explicit/fork head behavior, and
retry transient GitHub ref propagation
- disclose the push in the dialog and permission prompt, with focused
unit and visual coverage
- reject diverged remote branches without force-pushing

## Validation

- `pnpm test -- git-service pr-create-service permission-gate
follow-up-create-pr` — 233/233 passed
- full host-side `pnpm test` — 8,550/8,550 passed
- typecheck, lint, formatting, demo-site sync, dead-code, oracle, and
E2E syntax checks passed
- focused `create-pr-dialog.e2e.ts` Electron run — 1/1 passed
- updated dialog/completed-state screenshots visually reviewed

The sandboxed full test command reached the unit phase but its loopback
and fixed-temp-path tests were denied with `EPERM`; the host-side retry
passed the complete unit suite.

Co-Authored-By: Copse <noreply@copse.dev>
Copse-Models: acp:codex-acp#gpt-5.6-sol, acp:claude-acp#opus[1m]
Bumps the npm-minor-patch group with 8 updates:

| Package | From | To |
| --- | --- | --- |
|
[@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript)
| `0.117.1` | `0.122.0` |
|
[@wdio/mocha-framework](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/wdio-mocha-framework)
| `9.31.2` | `9.31.5` |
| [eslint](https://github.com/eslint/eslint) | `10.8.1` | `10.9.1` |
| [happy-dom](https://github.com/capricorn86/happy-dom) | `20.11.8` |
`20.12.0` |
| [openai](https://github.com/openai/openai-node) | `7.6.0` | `7.8.0` |
|
[webdriverio](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio)
| `9.31.3` | `9.31.5` |
| [zod](https://github.com/colinhacks/zod) | `4.4.3` | `4.5.4` |
|
[@electron-internal/extract-zip](https://github.com/electron/extract-zip)
| `1.0.4` | `1.0.5` |

Updates `@anthropic-ai/sdk` from 0.117.1 to 0.122.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/anthropic-sdk-typescript/releases">@​anthropic-ai/sdk's
releases</a>.</em></p>
<blockquote>
<h2>sdk: v0.122.0</h2>
<h2>0.122.0 (2026-08-27)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.121.0...sdk-v0.122.0">sdk-v0.121.0...sdk-v0.122.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> beta files/skills namespaces use GA shapes;
drop dated beta header pins (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/45d693a66bc7fb1af66d9c4e3625f6d6f64bae59">45d693a</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>client:</strong> classify cross-realm DOMException
abort/timeout errors correctly (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/1bd639515e9948c1dc45047ce5cb4fcfc5c436b8">1bd6395</a>)</li>
<li><strong>client:</strong> use configured logger for SSE parse errors
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/80eeaf7b01b49ed83abd38bb6841b4ad25b06fe6">80eeaf7</a>)</li>
<li><strong>sessions:</strong> make event accumulator forward-compatible
with new event types (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/410">#410</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/2d084fdaeb6b51a1613f321cd65e2184d55d8d1e">2d084fd</a>)</li>
<li><strong>tools:</strong> let read return a view_range of a file over
the size cap (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/427">#427</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/56b32601f6b97a0d56450f2eba19e58d1bb0202e">56b3260</a>)</li>
<li><strong>uploads:</strong> default bare Blob filename on skills
endpoints (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/422">#422</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/e1116491dd4021d0237d1bb440237677a03045f5">e111649</a>)</li>
<li><strong>webhooks:</strong> require headers to be passed to
<code>unwrap()</code> (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/3230804e63371f535a850d1df6752284643a01fe">3230804</a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><strong>internal:</strong> codegen related update (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/c2f327a30d14a4b1ee4cc777a4d0b37f5d44f0af">c2f327a</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li><strong>api:</strong> clarify pagination on the organization
rate-limit list endpoints (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/d6015b90b3e895cd173b7d71038ec71bf467a69b">d6015b9</a>)</li>
<li>document TypeScript 5.0 as the minimum supported version (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/788e7217937471e07af3800ab65172eafbf32e2a">788e721</a>)</li>
</ul>
<h2>sdk: v0.121.0</h2>
<h2>0.121.0 (2026-08-26)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.120.0...sdk-v0.121.0">sdk-v0.120.0...sdk-v0.121.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> add <code>updates</code> thinking display mode
(beta) (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/22274eaf968edc62a8b9e1f39a1913f5e04b2ada">22274ea</a>)</li>
<li><strong>api:</strong> add missing anthropic-beta values (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/0779f02666cfc6088c1555ee25e8cd8677e421da">0779f02</a>)</li>
<li><strong>api:</strong> add support for Organization API endpoints (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/8b61e7a4d441d662cd121c94c6d27dbffa5c294a">8b61e7a</a>)</li>
<li><strong>helpers:</strong> support Standard Schema for structured
outputs and tools (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/788007889c048e18f6bdf19d4903f1ac96f66cb0">7880078</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>tools:</strong> keep the tool runner going on pause_turn (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/288">#288</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/f040dc9d39a3ec471a87ac8eb93387fb7d9960c3">f040dc9</a>)</li>
<li><strong>types:</strong> don't reference NodeJS.ProcessEnv from
published types (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/367">#367</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/fd38dfdc48beec5d0d3640b65ac3e467bc73ea4e">fd38dfd</a>)</li>
</ul>
<h3>Chores</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md">@​anthropic-ai/sdk's
changelog</a>.</em></p>
<blockquote>
<h2>0.122.0 (2026-08-27)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.121.0...sdk-v0.122.0">sdk-v0.121.0...sdk-v0.122.0</a></p>
<h3>Features</h3>
<ul>
<li>
<p><strong>api:</strong> beta files/skills namespaces use GA shapes;
drop dated beta header pins (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/45d693a66bc7fb1af66d9c4e3625f6d6f64bae59">45d693a</a>)</p>
<p>The beta Files and Skills namespaces (<code>client.beta.files</code>,
<code>client.beta.skills</code>) no longer send the
<code>files-api-2025-04-14</code> / <code>skills-2025-10-02</code>
headers and return the same shapes as <code>client.files</code> /
<code>client.skills</code> (with <code>Beta</code>-prefixed type names).
Requests that still send those headers on raw HTTP keep receiving the
beta shapes.</p>
<p>Changes in the beta namespaces:</p>
<ul>
<li><code>client.beta.skills.delete()</code> now deletes a Skill
together with all of its versions (previously refused while any version
existed). It returns <code>BetaDeletedSkill</code> (was
<code>SkillDeleteResponse</code>).</li>
<li>Beta Messages type <code>BetaSkill</code> (the <code>{type,
skill_id, version}</code> entry in <code>BetaContainer.skills</code>) is
renamed <code>BetaContainerSkill</code>; the request-side
<code>BetaSkillParams</code> keeps its name. <code>BetaSkill</code> now
names the Skill object returned by
<code>client.beta.skills.create()</code> / <code>retrieve()</code> /
<code>list()</code> (replacing <code>SkillCreateResponse</code> /
<code>SkillRetrieveResponse</code> / <code>SkillListResponse</code>),
and skill versions are <code>BetaSkillVersion</code> /
<code>BetaDeletedSkillVersion</code> (replacing
<code>Version*Response</code>).</li>
<li><code>client.beta.files.list()</code> returns a
<code>BetaFileMetadataPageCursor</code>
(<code>PageCursor&lt;BetaFileMetadata&gt;</code> with <code>data</code>
/ <code>next_page</code>) and <code>FileListParams</code> paginates with
<code>page</code> / <code>ids</code> (was
<code>BetaFileMetadataPage</code>, a
<code>Page&lt;BetaFileMetadata&gt;</code> with <code>data</code>,
<code>has_more</code>, <code>first_id</code>, <code>last_id</code> and
<code>before_id</code> / <code>after_id</code>); <code>for await</code>
auto-pagination is unchanged. <code>BetaSkill</code> uses
<code>display_name</code> (was <code>display_title</code>, also in
<code>SkillCreateParams</code>) and <code>latest_version_id</code> (was
<code>latest_version</code>), and <code>BetaSkillVersion</code> is
addressed by its <code>skver_…</code> <code>id</code> (the
Unix-timestamp <code>version</code> field is gone).</li>
</ul>
<p>Migration guides: <a
href="https://platform.claude.com/docs/en/build-with-claude/files#migrate-from-files-api-2025-04-14">Migrate
from <code>files-api-2025-04-14</code></a> · <a
href="https://platform.claude.com/docs/en/build-with-claude/skills-guide#migrate-from-skills-2025-10-02">Migrate
from <code>skills-2025-10-02</code></a></p>
</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>client:</strong> classify cross-realm DOMException
abort/timeout errors correctly (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/1bd639515e9948c1dc45047ce5cb4fcfc5c436b8">1bd6395</a>)</li>
<li><strong>client:</strong> use configured logger for SSE parse errors
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/80eeaf7b01b49ed83abd38bb6841b4ad25b06fe6">80eeaf7</a>)</li>
<li><strong>sessions:</strong> make event accumulator forward-compatible
with new event types (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/410">#410</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/2d084fdaeb6b51a1613f321cd65e2184d55d8d1e">2d084fd</a>)</li>
<li><strong>tools:</strong> let read return a view_range of a file over
the size cap (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/427">#427</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/56b32601f6b97a0d56450f2eba19e58d1bb0202e">56b3260</a>)</li>
<li><strong>uploads:</strong> default bare Blob filename on skills
endpoints (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/422">#422</a>)
(<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/e1116491dd4021d0237d1bb440237677a03045f5">e111649</a>)</li>
<li><strong>webhooks:</strong> require headers to be passed to
<code>unwrap()</code> (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/3230804e63371f535a850d1df6752284643a01fe">3230804</a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><strong>internal:</strong> codegen related update (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/c2f327a30d14a4b1ee4cc777a4d0b37f5d44f0af">c2f327a</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li><strong>api:</strong> clarify pagination on the organization
rate-limit list endpoints (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/d6015b90b3e895cd173b7d71038ec71bf467a69b">d6015b9</a>)</li>
<li>document TypeScript 5.0 as the minimum supported version (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/788e7217937471e07af3800ab65172eafbf32e2a">788e721</a>)</li>
</ul>
<h2>0.121.0 (2026-08-26)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.120.0...sdk-v0.121.0">sdk-v0.120.0...sdk-v0.121.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> add <code>updates</code> thinking display mode
(beta) (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/22274eaf968edc62a8b9e1f39a1913f5e04b2ada">22274ea</a>)</li>
<li><strong>api:</strong> add missing anthropic-beta values (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/0779f02666cfc6088c1555ee25e8cd8677e421da">0779f02</a>)</li>
<li><strong>api:</strong> add support for Organization API endpoints (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/8b61e7a4d441d662cd121c94c6d27dbffa5c294a">8b61e7a</a>)</li>
<li><strong>helpers:</strong> support Standard Schema for structured
outputs and tools (<a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/788007889c048e18f6bdf19d4903f1ac96f66cb0">7880078</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/7f3898c525b1a1486c85b3808f03935418fed0b8"><code>7f3898c</code></a>
chore: release main</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/956ccb55b69dd087eee2eb892f9056b5455d123a"><code>956ccb5</code></a>
docs(api): clarify pagination on the organization rate-limit list
endpoints</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/2131d80a5e6fda409d3b81fda6a7b4572970ab5d"><code>2131d80</code></a>
fix(tools): let read return a view_range of a file over the size cap (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/427">#427</a>)</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/f830c8f561e6e7d2aca348ae75adeb12416b4597"><code>f830c8f</code></a>
fix(aws,bedrock): prefer env credentials over AWS_PROFILE (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/436">#436</a>)</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/4d50ddfb4f4dcc3baa37ecb0130053fde336d2cd"><code>4d50ddf</code></a>
test(ecosystem): expect cross-realm timeout errors to classify
correctly</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/acdd669d75026add9cfb2568cba80c1e80be838b"><code>acdd669</code></a>
fix(client): classify cross-realm DOMException abort/timeout errors
correctly</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/0a14622ea99bcd1e14d9eae83252adf0bb90276b"><code>0a14622</code></a>
test(tools): pin that read, edit and the memory tool preserve CRLF and
CR lin...</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/17035dea97538f1071dc341de93397934e12dbc6"><code>17035de</code></a>
docs: document TypeScript 5.0 as the minimum supported version</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/2f95d8a6f7774fc1b98fdb59e1881d1f8204e589"><code>2f95d8a</code></a>
fix(client): use configured logger for SSE parse errors</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-typescript/commit/33f510300959ce8ce2fdd8cc1b6192a189b209f7"><code>33f5103</code></a>
feat(api): beta files/skills namespaces use GA shapes; drop dated beta
header...</li>
<li>Additional commits viewable in <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.117.1...sdk-v0.122.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `@wdio/mocha-framework` from 9.31.2 to 9.31.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webdriverio/webdriverio/releases">@​wdio/mocha-framework's
releases</a>.</em></p>
<blockquote>
<h2>v9.31.5 (2026-08-29)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15535">#15535</a>
fix(webdriverio): handle BiDi no such node errors as stale (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15537">#15537</a>
fix(webdriverio): require all classic cookie filter attributes (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>wdio-protocols</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15536">#15536</a>
Docs/actions api guidance (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 3</h4>
<ul>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Fnine59 (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
<h2>v9.31.4 (2026-08-27)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15532">#15532</a>
feat: Beta ElementArray under feature flag for
<code>multiRemote.$$()</code> e.g. MultiRemoteElement[] (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>wdio-allure-reporter</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15278">#15278</a>
feat(wdio-allure-reporter): add support for global errors and global
attachments (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15478">#15478</a>
fix(webdriverio): track document element per context (<a
href="https://github.com/Badisi"><code>@​Badisi</code></a>)</li>
</ul>
</li>
<li><code>webdriver</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15516">#15516</a>
fix(webdriver): serialize IPv6 BiDi WebSocket candidates correctly (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15531">#15531</a>
docs(webdriverio): document the $$ async iterator methods (<a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 5</h4>
<ul>
<li>Alex (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
<li><a href="https://github.com/Badisi"><code>@​Badisi</code></a></li>
<li><a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a></li>
</ul>
<h2>v9.31.3 (2026-08-25)</h2>
<h4>:bug: Bug Fix</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/webdriverio/webdriverio/blob/main/CHANGELOG.md">@​wdio/mocha-framework's
changelog</a>.</em></p>
<blockquote>
<h2>v9.31.5 (2026-08-29)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15535">#15535</a>
fix(webdriverio): handle BiDi no such node errors as stale (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15537">#15537</a>
fix(webdriverio): require all classic cookie filter attributes (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>wdio-protocols</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15536">#15536</a>
Docs/actions api guidance (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 3</h4>
<ul>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Fnine59 (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
<h2>v9.31.4 (2026-08-27)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15532">#15532</a>
feat: Beta ElementArray under feature flag for
<code>multiRemote.$()</code> e.g. MultiRemoteElement[] (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>wdio-allure-reporter</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15278">#15278</a>
feat(wdio-allure-reporter): add support for global errors and global
attachments (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15478">#15478</a>
fix(webdriverio): track document element per context (<a
href="https://github.com/Badisi"><code>@​Badisi</code></a>)</li>
</ul>
</li>
<li><code>webdriver</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15516">#15516</a>
fix(webdriver): serialize IPv6 BiDi WebSocket candidates correctly (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15531">#15531</a>
docs(webdriverio): document the $ async iterator methods (<a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 5</h4>
<ul>
<li>Alex (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
<li><a href="https://github.com/Badisi"><code>@​Badisi</code></a></li>
<li><a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a></li>
</ul>
<h2>v9.31.3 (2026-08-25)</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/4f39140405a3a54a44c391f97a6de1fc3af2d33d"><code>4f39140</code></a>
v9.31.5</li>
<li>See full diff in <a
href="https://github.com/webdriverio/webdriverio/commits/v9.31.5/packages/wdio-mocha-framework">compare
view</a></li>
</ul>
</details>
<br />

Updates `eslint` from 10.8.1 to 10.9.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslint/releases">eslint's
releases</a>.</em></p>
<blockquote>
<h2>v10.9.1</h2>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/1e641c919fc1421493bf913feb607896982451a3"><code>1e641c9</code></a>
fix: no-loss-of-precision false positive with trailing decimal point (<a
href="https://redirect.github.com/eslint/eslint/issues/21251">#21251</a>)
(Aleksandr Shoronov)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/ad74a8dada2aaa17bfd0b8cc7b4119ff7a8ac04b"><code>ad74a8d</code></a>
docs: add deprecation steps for EOL package versions (<a
href="https://redirect.github.com/eslint/eslint/issues/21248">#21248</a>)
(Francesco Trotta)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/3c3ae53a43721162f0db76c69665ebd9d752ea52"><code>3c3ae53</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21249">#21249</a>)
(ESLint Bot)</li>
</ul>
<h2>v10.9.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/08de88e50294c4e01f6cae97eceeb578da55792b"><code>08de88e</code></a>
feat: handle underflow in no-loss-of-precision (<a
href="https://redirect.github.com/eslint/eslint/issues/21218">#21218</a>)
(Rithish S)</li>
<li><a
href="https://github.com/eslint/eslint/commit/55db4791120ae591d88089c43127b7b0e16866d4"><code>55db479</code></a>
feat: add checkConditionalExpressions to
<code>no-unmodified-loop-condition</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/21175">#21175</a>)
(sethamus)</li>
</ul>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/2ba302554e7a24e9909bbdd026fd0c2d1d0d8638"><code>2ba3025</code></a>
fix: prevent unsafe <code>no-var</code> autofix with hoisted functions
(<a
href="https://redirect.github.com/eslint/eslint/issues/21213">#21213</a>)
(sethamus)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8e6962219a605c5f5add10953aa31027da839194"><code>8e69622</code></a>
fix: Prevent no-var autofix when var is shadowed by catch parameter (<a
href="https://redirect.github.com/eslint/eslint/issues/21204">#21204</a>)
(Yang Hyeonjong)</li>
<li><a
href="https://github.com/eslint/eslint/commit/684b57972e1ddf25e076fb36189c60bbcacee635"><code>684b579</code></a>
fix: prefer-template invalid autofix creates a tagged template call (<a
href="https://redirect.github.com/eslint/eslint/issues/21207">#21207</a>)
(김채영)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/9ef407a3b051e74f50dc7fb8914e2bd89b3e5e53"><code>9ef407a</code></a>
docs: use eslint.config.* wherever config file names are listed (<a
href="https://redirect.github.com/eslint/eslint/issues/21216">#21216</a>)
(Marry (Subin Yang))</li>
<li><a
href="https://github.com/eslint/eslint/commit/87f66f4435c4df7f4f6815c939d153196ec03e3c"><code>87f66f4</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/585ef37516c0dc29ddb91ce2a2cdcc46fdbbd610"><code>585ef37</code></a>
docs: update architecture documentation (<a
href="https://redirect.github.com/eslint/eslint/issues/21112">#21112</a>)
(Francesco Trotta)</li>
<li><a
href="https://github.com/eslint/eslint/commit/f3993b0547bace7370e9728ee7408af49d367d76"><code>f3993b0</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/ffc87d6234b2aa4335eec069e5c4d6ac04832b9e"><code>ffc87d6</code></a>
docs: fix broken links in Further Reading sections (<a
href="https://redirect.github.com/eslint/eslint/issues/21203">#21203</a>)
(Minsu)</li>
<li><a
href="https://github.com/eslint/eslint/commit/1a761e1d11b011fcb6bee181231a51010c500e4d"><code>1a761e1</code></a>
docs: update moved JSX specification links (<a
href="https://redirect.github.com/eslint/eslint/issues/21198">#21198</a>)
(Imran Mustafa)</li>
<li><a
href="https://github.com/eslint/eslint/commit/4d00ca4064ae0d1a75b604a16c68ab9f386ad388"><code>4d00ca4</code></a>
docs: update ESLint peer dependency to <code>^10.0.0</code> in shareable
configs (<a
href="https://redirect.github.com/eslint/eslint/issues/21202">#21202</a>)
(lumir)</li>
<li><a
href="https://github.com/eslint/eslint/commit/510d1a2e87bc197219f42e195ddb638d2b183a5a"><code>510d1a2</code></a>
docs: Update README (GitHub Actions Bot)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/899dbf131ce12a194b394bb8d67307df23509d17"><code>899dbf1</code></a>
chore: update github/codeql-action action to v4.37.7 (<a
href="https://redirect.github.com/eslint/eslint/issues/21243">#21243</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/9aa38732177935bd1d7f1493732c0b67666be28a"><code>9aa3873</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21235">#21235</a>)
(ESLint Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/dc1e7a8416937edefe04cf836ee202a6fc03bedd"><code>dc1e7a8</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21208">#21208</a>)
(ESLint Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/f878d212e9622da9513bcd60d2aedb2e8bb4fc8b"><code>f878d21</code></a>
ci: bump pnpm/action-setup from 6.0.9 to 6.0.10 (<a
href="https://redirect.github.com/eslint/eslint/issues/21200">#21200</a>)
(dependabot[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/4891e50aceadb0e886ad7d8ab5ae2beab563de85"><code>4891e50</code></a>
ci: bump github/codeql-action from 4.37.4 to 4.37.6 (<a
href="https://redirect.github.com/eslint/eslint/issues/21199">#21199</a>)
(dependabot[bot])</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/5c8c2417b9ff462f2dc4e54a062c59135b45b845"><code>5c8c241</code></a>
10.9.1</li>
<li><a
href="https://github.com/eslint/eslint/commit/a7f3b7ddca7de8464995707d1bbac3ca91090015"><code>a7f3b7d</code></a>
Build: changelog update for 10.9.1</li>
<li><a
href="https://github.com/eslint/eslint/commit/1e641c919fc1421493bf913feb607896982451a3"><code>1e641c9</code></a>
fix: no-loss-of-precision false positive with trailing decimal point (<a
href="https://redirect.github.com/eslint/eslint/issues/21251">#21251</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/ad74a8dada2aaa17bfd0b8cc7b4119ff7a8ac04b"><code>ad74a8d</code></a>
docs: add deprecation steps for EOL package versions (<a
href="https://redirect.github.com/eslint/eslint/issues/21248">#21248</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/3c3ae53a43721162f0db76c69665ebd9d752ea52"><code>3c3ae53</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21249">#21249</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c27bc926e496985eb7911c09eb60914b2e4b5d0f"><code>c27bc92</code></a>
10.9.0</li>
<li><a
href="https://github.com/eslint/eslint/commit/fa831d95b326e6d23671d9b2df1ea5dbc64f6f34"><code>fa831d9</code></a>
Build: changelog update for 10.9.0</li>
<li><a
href="https://github.com/eslint/eslint/commit/899dbf131ce12a194b394bb8d67307df23509d17"><code>899dbf1</code></a>
chore: update github/codeql-action action to v4.37.7 (<a
href="https://redirect.github.com/eslint/eslint/issues/21243">#21243</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/08de88e50294c4e01f6cae97eceeb578da55792b"><code>08de88e</code></a>
feat: handle underflow in no-loss-of-precision (<a
href="https://redirect.github.com/eslint/eslint/issues/21218">#21218</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9ef407a3b051e74f50dc7fb8914e2bd89b3e5e53"><code>9ef407a</code></a>
docs: use eslint.config.* wherever config file names are listed (<a
href="https://redirect.github.com/eslint/eslint/issues/21216">#21216</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslint/compare/v10.8.1...v10.9.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `happy-dom` from 20.11.8 to 20.12.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/capricorn86/happy-dom/releases">happy-dom's
releases</a>.</em></p>
<blockquote>
<h2>v20.12.0</h2>
<h3>:art: Features</h3>
<ul>
<li>Adds support for <code>Animation</code>,
<code>AnimationTimeline</code>, <code>DocumentTimeline</code> and
<code>KeyframeEffect</code> - By <strong><a
href="https://github.com/capricorn86"><code>@​capricorn86</code></a></strong></li>
<li>Adds support for <code>Element.animate()</code> - By <strong><a
href="https://github.com/capricorn86"><code>@​capricorn86</code></a></strong></li>
</ul>
<h2>v20.11.15</h2>
<h3>:construction_worker_man: Patch fixes</h3>
<ul>
<li>Fixes issue with upgrading custom element caused by previous release
- By <strong><a
href="https://github.com/capricorn86"><code>@​capricorn86</code></a></strong>
in task <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2331">#2331</a></li>
</ul>
<h2>v20.11.14</h2>
<h3>:construction_worker_man: Patch fixes</h3>
<ul>
<li>Change prototype of element to the custom element when upgraded to
keep the reference - By <strong><a
href="https://github.com/capricorn86"><code>@​capricorn86</code></a></strong>
in task <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2331">#2331</a>
<ul>
<li>The previous solution replaced the element with the custom element
and copied it's properties, but this made any reference to the original
element invalid</li>
</ul>
</li>
</ul>
<h2>v20.11.13</h2>
<h3>:construction_worker_man: Patch fixes</h3>
<ul>
<li>Copy event listeners when upgrading a custom element - By <strong><a
href="https://github.com/capricorn86"><code>@​capricorn86</code></a></strong>
in task <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2329">#2329</a></li>
</ul>
<h2>v20.11.12</h2>
<h3>:construction_worker_man: Patch fixes</h3>
<ul>
<li>Add check for if the node is still a parent during disconnect - By
<strong><a
href="https://github.com/capricorn86"><code>@​capricorn86</code></a></strong>
in task <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2322">#2322</a></li>
</ul>
<h2>v20.11.11</h2>
<h3>:construction_worker_man: Patch fixes</h3>
<ul>
<li>Use proxy as parent node in Node.connectedToNode - By <strong><a
href="https://github.com/diegohaz"><code>@​diegohaz</code></a></strong>
in task <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2253">#2253</a></li>
</ul>
<h2>v20.11.10</h2>
<h3>:construction_worker_man: Patch fixes</h3>
<ul>
<li>Return default validationMessage for built-in constraint violations
- By <strong><a
href="https://github.com/mixelburg"><code>@​mixelburg</code></a></strong>
in task <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2187">#2187</a></li>
</ul>
<h2>v20.11.9</h2>
<h3>:construction_worker_man: Patch fixes</h3>
<ul>
<li>Make sure that the element has been upgraded before invoking
reaction callback in custom elements - By <strong><a
href="https://github.com/capricorn86"><code>@​capricorn86</code></a></strong>
in task <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2317">#2317</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/4b0af0ce51268e897bf129c43d29644555b7bb48"><code>4b0af0c</code></a>
feat: Adds support for Element.animate (<a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2335">#2335</a>)</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/e1a8af5cb664bf76d72404b86c499e739a4e4ed2"><code>e1a8af5</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2331">#2331</a>
Fixes issue with upgrading custom element caused by previous rel...</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/096317b80d642c919f6641b23e4154a143e3c9cf"><code>096317b</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2331">#2331</a>
Change prototype of element to the custom element when upgraded ...</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/9ff4b4752c2c00b5bb03b309d40fa82f05c15de7"><code>9ff4b47</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2329">#2329</a>
Copy event listeners when upgrading a custom element (<a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2330">#2330</a>)</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/3d282e5ba993b33649d2afe9d3367b0a10cbff5b"><code>3d282e5</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2322">#2322</a>
Add check for if the node is still a parent during disconnect (#...</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/64e2c774cadbb8eda5416c1e2bcca5006d1b5df9"><code>64e2c77</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2253">#2253</a>
Use proxy as parent node in Node.connectedToNode() (<a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2254">#2254</a>)</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/a00ec699ffcaaabdb6e6b7707df1ce832e39d00f"><code>a00ec69</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2319">#2319</a>
Removing a child incorrectly calls flow for connecting node (<a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2321">#2321</a>)</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/152b5d8a9e2690ba2039867f42e3e2a8e9b7c274"><code>152b5d8</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2319">#2319</a>
Removing a child incorrectly calls flow for connecting node (<a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2320">#2320</a>)</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/1100b9c30182b63121dfa826b2a7fc7a8c76dd6e"><code>1100b9c</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2187">#2187</a>
Return default validationMessage for built-in constraint violati...</li>
<li><a
href="https://github.com/capricorn86/happy-dom/commit/189379545b74351a2f00c88a37c4732a3e185e41"><code>1893795</code></a>
fix: <a
href="https://redirect.github.com/capricorn86/happy-dom/issues/2317">#2317</a>
Make sure that the element has been upgraded before invoking rea...</li>
<li>See full diff in <a
href="https://github.com/capricorn86/happy-dom/compare/v20.11.8...v20.12.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `openai` from 7.6.0 to 7.8.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/openai/openai-node/releases">openai's
releases</a>.</em></p>
<blockquote>
<h2>v7.8.0</h2>
<h2><a
href="https://github.com/openai/openai-node/compare/v7.7.0...v7.8.0">7.8.0</a>
(2026-08-27)</h2>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> add compute_units to Responses and Chat
Completions usage (<a
href="https://redirect.github.com/openai/openai-node/issues/2505">#2505</a>)
(<a
href="https://github.com/openai/openai-node/commit/f9b131388a270451eec7242aa0d302e2135b1d3e">f9b1313</a>)</li>
<li><strong>api:</strong> add default WebSocket User-Agent and audit log
events (<a
href="https://redirect.github.com/openai/openai-node/issues/2504">#2504</a>)
(<a
href="https://github.com/openai/openai-node/commit/9b2f0892c9fde399163bea59d4aa9bc4ba0ae03e">9b2f089</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>auth:</strong> clamp the workload-identity refresh buffer to
the token lifetime (<a
href="https://redirect.github.com/openai/openai-node/issues/2490">#2490</a>)
(<a
href="https://github.com/openai/openai-node/commit/76b73a9c73aafe11c60a425a82898705f1db966d">76b73a9</a>)</li>
<li><strong>auth:</strong> secure first-class X.509 workload credentials
(<a
href="https://redirect.github.com/openai/openai-node/issues/2479">#2479</a>)
(<a
href="https://github.com/openai/openai-node/commit/1b36c19c011bcb9775a5cecb0d8a1bd072b74cd6">1b36c19</a>)</li>
<li><strong>deps:</strong> harden dependency and release boundaries (<a
href="https://redirect.github.com/openai/openai-node/issues/2484">#2484</a>)
(<a
href="https://github.com/openai/openai-node/commit/b21ff456e7bd578547c456e5f7abd1d494a02090">b21ff45</a>)</li>
<li><strong>events:</strong> settle WebSocket waiters when listeners
throw (<a
href="https://redirect.github.com/openai/openai-node/issues/2491">#2491</a>)
(<a
href="https://github.com/openai/openai-node/commit/b5a13a7dc8c95669440ec87662bcae8227b398d9">b5a13a7</a>)</li>
<li>restore native browser ESM imports (<a
href="https://redirect.github.com/openai/openai-node/issues/2495">#2495</a>)
(<a
href="https://github.com/openai/openai-node/commit/c8cab1e90eb5daf05d17d5c90492e6728afa91b7">c8cab1e</a>)</li>
<li><strong>uploads:</strong> detect multipart bodies from own
properties only (<a
href="https://redirect.github.com/openai/openai-node/issues/2492">#2492</a>)
(<a
href="https://github.com/openai/openai-node/commit/559ffc8079d0ae716bbf7915305e7db6ecc016a1">559ffc8</a>)</li>
</ul>
<h2>v7.7.0</h2>
<h2><a
href="https://github.com/openai/openai-node/compare/v7.6.0...v7.7.0">7.7.0</a>
(2026-08-26)</h2>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> make function call output call IDs optional
(<a
href="https://redirect.github.com/openai/openai-node/issues/2488">#2488</a>)
(<a
href="https://github.com/openai/openai-node/commit/b932ae62bc4054da760c1f82329b1cc24bc05fbe">b932ae6</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>streaming:</strong> honor cancellation while decoding SSE
responses (<a
href="https://redirect.github.com/openai/openai-node/issues/2487">#2487</a>)
(<a
href="https://github.com/openai/openai-node/commit/ababe129507a48d6c4d2be12bbef2ae442c1632f">ababe12</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/openai/openai-node/blob/main/CHANGELOG.md">openai's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/openai/openai-node/compare/v7.7.0...v7.8.0">7.8.0</a>
(2026-08-27)</h2>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> add compute_units to Responses and Chat
Completions usage (<a
href="https://redirect.github.com/openai/openai-node/issues/2505">#2505</a>)
(<a
href="https://github.com/openai/openai-node/commit/f9b131388a270451eec7242aa0d302e2135b1d3e">f9b1313</a>)</li>
<li><strong>api:</strong> add default WebSocket User-Agent and audit log
events (<a
href="https://redirect.github.com/openai/openai-node/issues/2504">#2504</a>)
(<a
href="https://github.com/openai/openai-node/commit/9b2f0892c9fde399163bea59d4aa9bc4ba0ae03e">9b2f089</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>auth:</strong> clamp the workload-identity refresh buffer to
the token lifetime (<a
href="https://redirect.github.com/openai/openai-node/issues/2490">#2490</a>)
(<a
href="https://github.com/openai/openai-node/commit/76b73a9c73aafe11c60a425a82898705f1db966d">76b73a9</a>)</li>
<li><strong>auth:</strong> secure first-class X.509 workload credentials
(<a
href="https://redirect.github.com/openai/openai-node/issues/2479">#2479</a>)
(<a
href="https://github.com/openai/openai-node/commit/1b36c19c011bcb9775a5cecb0d8a1bd072b74cd6">1b36c19</a>)</li>
<li><strong>deps:</strong> harden dependency and release boundaries (<a
href="https://redirect.github.com/openai/openai-node/issues/2484">#2484</a>)
(<a
href="https://github.com/openai/openai-node/commit/b21ff456e7bd578547c456e5f7abd1d494a02090">b21ff45</a>)</li>
<li><strong>events:</strong> settle WebSocket waiters when listeners
throw (<a
href="https://redirect.github.com/openai/openai-node/issues/2491">#2491</a>)
(<a
href="https://github.com/openai/openai-node/commit/b5a13a7dc8c95669440ec87662bcae8227b398d9">b5a13a7</a>)</li>
<li>restore native browser ESM imports (<a
href="https://redirect.github.com/openai/openai-node/issues/2495">#2495</a>)
(<a
href="https://github.com/openai/openai-node/commit/c8cab1e90eb5daf05d17d5c90492e6728afa91b7">c8cab1e</a>)</li>
<li><strong>uploads:</strong> detect multipart bodies from own
properties only (<a
href="https://redirect.github.com/openai/openai-node/issues/2492">#2492</a>)
(<a
href="https://github.com/openai/openai-node/commit/559ffc8079d0ae716bbf7915305e7db6ecc016a1">559ffc8</a>)</li>
</ul>
<h2><a
href="https://github.com/openai/openai-node/compare/v7.6.0...v7.7.0">7.7.0</a>
(2026-08-26)</h2>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> make function call output call IDs optional
(<a
href="https://redirect.github.com/openai/openai-node/issues/2488">#2488</a>)
(<a
href="https://github.com/openai/openai-node/commit/b932ae62bc4054da760c1f82329b1cc24bc05fbe">b932ae6</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>streaming:</strong> honor cancellation while decoding SSE
responses (<a
href="https://redirect.github.com/openai/openai-node/issues/2487">#2487</a>)
(<a
href="https://github.com/openai/openai-node/commit/ababe129507a48d6c4d2be12bbef2ae442c1632f">ababe12</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/openai/openai-node/commit/31fae5367989ec503a4369a7ad7cb959b25b2878"><code>31fae53</code></a>
release: 7.8.0 (<a
href="https://redirect.github.com/openai/openai-node/issues/2493">#2493</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/f9b131388a270451eec7242aa0d302e2135b1d3e"><code>f9b1313</code></a>
feat(api): add compute_units to Responses and Chat Completions usage (<a
href="https://redirect.github.com/openai/openai-node/issues/2505">#2505</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/b21ff456e7bd578547c456e5f7abd1d494a02090"><code>b21ff45</code></a>
fix(deps): harden dependency and release boundaries (<a
href="https://redirect.github.com/openai/openai-node/issues/2484">#2484</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/a4a80ebbb7491b015de63c2f4f07348d570faf9b"><code>a4a80eb</code></a>
test: exercise direct browser imports without credentials (<a
href="https://redirect.github.com/openai/openai-node/issues/2501">#2501</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/c8cab1e90eb5daf05d17d5c90492e6728afa91b7"><code>c8cab1e</code></a>
fix: restore native browser ESM imports (<a
href="https://redirect.github.com/openai/openai-node/issues/2495">#2495</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/d691a1067630680eb00e9c9e04b65a709ae6a4b0"><code>d691a10</code></a>
test: execute webpack browser bundle without live credentials (<a
href="https://redirect.github.com/openai/openai-node/issues/2498">#2498</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/7ef99c48f76146a409763bb28f388837a2002bbd"><code>7ef99c4</code></a>
test: verify packed browser bundles target ES2020 (<a
href="https://redirect.github.com/openai/openai-node/issues/2496">#2496</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/b86efacacbe1fa8374e99188161884cfd25b594e"><code>b86efac</code></a>
ci: require keyless ecosystem checks on release PRs (<a
href="https://redirect.github.com/openai/openai-node/issues/2497">#2497</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/08f201c55556bc1c8086295d8e76842997f7fea5"><code>08f201c</code></a>
ci: gate npm publication on browser-compatible imports (<a
href="https://redirect.github.com/openai/openai-node/issues/2500">#2500</a>)</li>
<li><a
href="https://github.com/openai/openai-node/commit/44af438bf0680e0a89a2f4697a854bfb6c18a046"><code>44af438</code></a>
test: cover webpack ESM and CommonJS package entrypoints (<a
href="https://redirect.github.com/openai/openai-node/issues/2499">#2499</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/openai/openai-node/compare/v7.6.0...v7.8.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `webdriverio` from 9.31.3 to 9.31.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webdriverio/webdriverio/releases">webdriverio's
releases</a>.</em></p>
<blockquote>
<h2>v9.31.5 (2026-08-29)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15535">#15535</a>
fix(webdriverio): handle BiDi no such node errors as stale (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15537">#15537</a>
fix(webdriverio): require all classic cookie filter attributes (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>wdio-protocols</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15536">#15536</a>
Docs/actions api guidance (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 3</h4>
<ul>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Fnine59 (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
<h2>v9.31.4 (2026-08-27)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15532">#15532</a>
feat: Beta ElementArray under feature flag for
<code>multiRemote.$$()</code> e.g. MultiRemoteElement[] (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>wdio-allure-reporter</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15278">#15278</a>
feat(wdio-allure-reporter): add support for global errors and global
attachments (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15478">#15478</a>
fix(webdriverio): track document element per context (<a
href="https://github.com/Badisi"><code>@​Badisi</code></a>)</li>
</ul>
</li>
<li><code>webdriver</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15516">#15516</a>
fix(webdriver): serialize IPv6 BiDi WebSocket candidates correctly (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15531">#15531</a>
docs(webdriverio): document the $$ async iterator methods (<a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 5</h4>
<ul>
<li>Alex (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
<li><a href="https://github.com/Badisi"><code>@​Badisi</code></a></li>
<li><a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/webdriverio/webdriverio/blob/main/CHANGELOG.md">webdriverio's
changelog</a>.</em></p>
<blockquote>
<h2>v9.31.5 (2026-08-29)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>wdio-utils</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15538">#15538</a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15535">#15535</a>
fix(webdriverio): handle BiDi no such node errors as stale (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15537">#15537</a>
fix(webdriverio): require all classic cookie filter attributes (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>wdio-protocols</code>, <code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15536">#15536</a>
Docs/actions api guidance (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 3</h4>
<ul>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Fnine59 (<a
href="https://github.com/Fnine59"><code>@​Fnine59</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
<h2>v9.31.4 (2026-08-27)</h2>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15532">#15532</a>
feat: Beta ElementArray under feature flag for
<code>multiRemote.$()</code> e.g. MultiRemoteElement[] (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
</ul>
</li>
<li><code>wdio-allure-reporter</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15278">#15278</a>
feat(wdio-allure-reporter): add support for global errors and global
attachments (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15478">#15478</a>
fix(webdriverio): track document element per context (<a
href="https://github.com/Badisi"><code>@​Badisi</code></a>)</li>
</ul>
</li>
<li><code>webdriver</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15516">#15516</a>
fix(webdriver): serialize IPv6 BiDi WebSocket candidates correctly (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><code>webdriverio</code>
<ul>
<li><a
href="https://redirect.github.com/webdriverio/webdriverio/pull/15531">#15531</a>
docs(webdriverio): document the $ async iterator methods (<a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 5</h4>
<ul>
<li>Alex (<a
href="https://github.com/todti"><code>@​todti</code></a>)</li>
<li>David Prevost (<a
href="https://github.com/dprevost-LMI"><code>@​dprevost-LMI</code></a>)</li>
<li>Martsin Lazouski (<a
href="https://github.com/ML642"><code>@​ML642</code></a>)</li>
<li><a href="https://github.com/Badisi"><code>@​Badisi</code></a></li>
<li><a
href="https://github.com/devangpratap"><code>@​devangpratap</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/4f39140405a3a54a44c391f97a6de1fc3af2d33d"><code>4f39140</code></a>
v9.31.5</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/71d86bcb9fb7f6eeee72515b1475fb511e2b61a1"><code>71d86bc</code></a>
feat: UNSTABLE Review Multi-remote <code>select</code> implementations
and remove filter...</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/8850df025e9a8c9007e712f439a9167684065b66"><code>8850df0</code></a>
fix(webdriverio): handle BiDi no such node errors as stale (<a
href="https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio/issues/15535">#15535</a>)</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/8958aba3b13a4439995cc4f818eec2cdfade7282"><code>8958aba</code></a>
Docs/actions api guidance (<a
href="https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio/issues/15536">#15536</a>)</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/719be34eda44cd26985a5d1cbe1c6d43572e713e"><code>719be34</code></a>
fix(webdriverio): require all classic cookie filter attributes (<a
href="https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio/issues/15537">#15537</a>)</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/69c45e316f75ba2e40e83ef7475106ece26cf2e2"><code>69c45e3</code></a>
v9.31.4</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/662093ba165cefeb62ab4be57d66f5f545ddcfc1"><code>662093b</code></a>
feat: Beta ElementArray under feature flag for
<code>multiRemote.$$()</code> e.g. MultiR...</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/dad1df66d0c19f43a4aff427fbc1761393a28d9d"><code>dad1df6</code></a>
docs(webdriverio): document the $$ async iterator methods (<a
href="https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio/issues/15531">#15531</a>)</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/de2c6a8b19d5eccaf6007e50af91aaa9b8545fd9"><code>de2c6a8</code></a>
Fix test since instances order is no garantee (<a
href="https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio/issues/15530">#15530</a>)</li>
<li><a
href="https://github.com/webdriverio/webdriverio/commit/c457b129c90b586701e53ac7b98ae4309941df8a"><code>c457b12</code></a>
fix(webdriverio): track document element per context (<a
href="https://github.com/webdriverio/webdriverio/tree/HEAD/packages/webdriverio/issues/15478">#15478</a>)</li>
<li>See full diff in <a
href="https://github.com/webdriverio/webdriverio/commits/v9.31.5/packages/webdriverio">compare
view</a></li>
</ul>
</details>
<br />

Updates `zod` from 4.4.3 to 4.5.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/colinhacks/zod/releases">zod's
releases</a>.</em></p>
<blockquote>
<h2>v4.5.4</h2>
<h2>Commits:</h2>
<ul>
<li>84e416fbf4740527bbc8f319634f4e1b065bb42c fix(v4): stop the cycle
walk from firing a default factory (<a
href="https://redirect.github.com/colinhacks/zod/issues/6500">#6500</a>)</li>
<li>e8e206fa33ac5fe7ce20a2beb12d57b1cb3df653 4.5.4</li>
</ul>
<h2>v4.5.3</h2>
<h2>Commits:</h2>
<ul>
<li>e6b6ab347675cd2bd54b1bdbed16f98c59be82a9 docs(blog): widen the
z.compile example to a 20-property schema</li>
<li>87d6464418582bb96fc665a01f852ca6da324ad0 fix(docs): drop the OG
description when the title wraps past two lines</li>
<li>99fce394a026823e602b9c30d8d5d9f5f1932ce7 bench(v4): z.compile()
against zod-compiler (<a
href="https://redirect.github.com/colinhacks/zod/issues/6499">#6499</a>)</li>
<li>e3a695b6bf3f0d591ea682816e3cdaea04b0f967 docs(v4): record the email
regex and container output-shape findings under Open</li>
<li>7e24a24288183ce02554f1ded7775d0650a7b7e6 docs(blog): drop the
reading time and put a GitHub link in the navbar</li>
<li>eab51ff3592b2d11d863f4ee4d5452f31a3de1b6 fix(v4): emit record
numeric keys as strings in toJSONSchema (<a
href="https://redirect.github.com/colinhacks/zod/issues/6497">#6497</a>)</li>
</ul>
<h2>v4.5.2</h2>
<h2>Commits:</h2>
<ul>
<li>a354314ac04fdd5484aa62dd5c3a4b553211a0e4 fix(docs): keep blog posts
out of the docs collection (<a
href="https://redirect.github.com/colinhacks/zod/issues/6484">#6484</a>)</li>
<li>d378c42aff6869f0929058a7923cd775880f5c4c ci: drop canary publishing
from the release workflow (<a
href="https://redirect.github.com/colinhacks/zod/issues/6487">#6487</a>)</li>
<li>212b941791e7faae078e17645eb612824fd8f79a fix(v4): let a prototype
method getter answer a bare call so vi.spyOn works (<a
href="https://redirect.github.com/colinhacks/zod/issues/6488">#6488</a>)</li>
<li>e7576f542a7bc7ef3cc5eeec237714fd0e6b6e98 docs(blog): let the page
show through the navbar in dark mode (<a
href="https://redirect.github.com/colinhacks/zod/issues/6489">#6489</a>)</li>
<li>fedb06fafe33a66ce0b5c236ad2557e0a5a170fe fix(docs): match the blog
TOC hover bar to the 2px active indicator</li>
<li>6c932fcb2eea6eb671710ea058ca9fdc382ada89 chore: bump devcontainer
image to Node 24 (<a
href="https://redirect.github.com/colinhacks/zod/issues/6470">#6470</a>)</li>
<li>6635d9dd367a664109de83c021995821f48efa29 docs(blog): soften the
&quot;method memoization&quot; attribution</li>
<li>019ae299cc75daa132bf1acf59086a520abf6b85 fix(docs): drop ISR on the
docs route so the home page hydrates</li>
<li>652bb438aa4c626c1cd7948c6849c4691239fca7 chore(docs): drop the
scroll log from the route-change scroller</li>
<li>571c8e8a3d73b4305f4abfdd6977773cc12f2bf5 fix(docs): render blog tabs
with the stock fumadocs tab card</li>
<li>9a193aa24b4efa3b315b91d4c56c8bc385b8513f 4.5.2</li>
</ul>
<h2>v4.5.1</h2>
<h2>Commits:</h2>
<ul>
<li>2e862dbf89da2835e5206a8fd3d3be61afe3cf7f ci: gate the GitHub release
and JSR publish on the version being live on npm</li>
<li>8e03380510db36fa6fda979fc78a375fdea8021c 4.5.1</li>
</ul>
<h2>v4.5.0</h2>
<p>Zod 4.5 is now available.</p>
<pre lang="sh"><code>npm install zod@latest
</code></pre>
<p>At a glance:</p>
<ul>
<li><a
href="https://github.com/colinhacks/zod/blob/HEAD%5Bhttps://github.com/colinhacks/zod/blob/HEAD%60https://github.com/colinhacks/zod/blob/HEADzhttps://github.com/colinhacks/zod/blob/HEAD.https://github.com/colinhacks/zod/blob/HEADchttps://github.com/colinhacks/zod/blob/HEADohttps://github.com/colinhacks/zod/blob/HEADmhttps://github.com/colinhacks/zod/blob/HEADphttps://github.com/colinhacks/zod/blob/HEADihttps://github.com/colinhacks/zod/blob/HEADlhttps://github.com/colinhacks/zod/blob/HEADehttps://github.com/colinhacks/zod/blob/HEAD(https://github.com/colinhacks/zod/blob/HEAD)https://github.com/colinhacks/zod/blob/HEAD%60https://github.com/colinhacks/zod/blob/HEAD%5Dhttps://github.com/colinhacks/zod/blob/HEAD(https://github.com/colinhacks/zod/blob/HEAD#https://github.com/colinhacks/zod/blob/HEADzhttps://github.com/colinhacks/zod/blob/HEADchttps://github.com/colinhacks/zod/blob/HEADohttps://github.com/colinhacks/zod/blob/HEADmhttps://github.com/colinhacks/zod/blob/HEADphttps://github.com/colinhacks/zod/blob/HEADihttps://github.com/colinhacks/zod/blob/HEADlhttps://github.com/colinhacks/zod/blob/HEADehttps://github.com/colinhacks/zod/blob/HEAD)https://github.com/colinhacks/zod/blob/HEAD">https://github.com/colinhacks/zod/blob/HEAD[https://github.com/colinhacks/zod/blob/HEAD`https://github.com/colinhacks/zod/blob/HEADzhttps://github.com/colinhacks/zod/blob/HEAD.https://github.com/colinhacks/zod/blob/HEADchttps://github.com/colinhacks/zod/blob/HEADohttps://github.com/colinhacks/zod/blob/HEADmhttps://github.com/colinhacks/zod/blob/HEADphttps://github.com/colinhacks/zod/blob/HEADihttps://github.com/colinhacks/zod/blob/HEADlhttps://github.com/colinhacks/zod/blob/HEADehttps://github.com/colinhacks/zod/blob/HEAD(https://github.com/colinhacks/zod/blob/HEAD)https://github.com/colinhacks/zod/blob/HEAD`https://github.com/colinhacks/zod/blob/HEAD]https://github.com/colinhacks/zod/blob/HEAD(https://github.com/colinhacks/zod/blob/HEAD#https://github.com/colinhacks/zod/blob/HEADzhttps://github.com/colinhacks/zod/blob/HEADchttps://github.com/colinhacks/zod/blob/HEADohttps://github.com/colinhacks/zod/blob/HEADmhttps://github.com/colinhacks/zod/blob/HEADphttps://github.com/colinhacks/zod/blob/HEADihttps://github.com/colinhacks/zod/blob/HEADlhttps://github.com/colinhacks/zod/blob/HEADehttps://github.com/colinhacks/zod/blob/HEAD)https://github.com/colinhacks/zod/blob/HEAD</a>
— the flagship feature of Zod 4.5</li>
<li><a
href="https://github.com/colinhacks/zod/blob/HEAD%5Bhttps://github.com/colinhacks/zod/blob/HEAD%60https://github.com/colinhacks/zod/blob/HEADzhttps://github.com/colinhacks/zod/blob/HEAD.https://github.com/colinhacks/zod/blob/HEADchttps://github.com/colinhacks/zod/blob/HEADrhttps://github.com/colinhacks/zod/blob/HEADehttps://github.com/colinhacks/zod/blob/HEADdhttps://github.com/colinhacks/zod/blob/HEADihttps://github.com/colinhacks/zod/blob/HEADthttps://github.com/colinhacks/zod/blob/HEADChttps://github.com/colinhacks/zod/blob/HEADahttps://github.com/colinhacks/zod/blob/HEADrhttps://github.com/colinhacks/zod/blob/HEADdhttps://github.com/colinhacks/zod/blob/HEAD(https://github.com/colinhacks/zod/blob/HEAD)https://github.com/colinhacks/zod/blob/HEAD%60https://github.com/colinhacks/zod/blob/HEAD%5Dhttps://github.com/colinhacks/zod/blob/HEAD(https://github.com/colinhacks/zod/blob/HEAD#https://github.com/colinhacks/zod/blob/HEADzhttps://github.com/colinhacks/zod/blob/HEADchttps://github.com/colinhacks/zod/blob/HEADrhttps://github.com/colinhacks/zod/blob/HEADehttps://gith…
## Problem

WebDriver reference captures inherit the host display's device-pixel
ratio. Retina macOS runs therefore write 2x PNGs while Linux CI rewrites
the same references at 1x, leaving mixed baselines and periodic
screenshot-only churn.

## Fix

- launch Electron with a fixed 2x device scale factor on every E2E host
- fail fast at session startup if Chromium does not report the expected
DPR
- run headless Linux E2E under a 3200x2048 Xvfb framebuffer, preserving
the existing 1200x800 and 1600x800 CSS layouts at 2x
- route the local, CI, and markdown E2E scripts through the same
cross-platform launcher

## Validation

- `pnpm run check` — 8,550/8,550 tests passed, including typecheck,
lint, formatting, dead-code, oracle, and E2E syntax gates; repeated
after rebasing onto latest `origin/main`
- `pnpm run build`
- `pnpm run test:e2e -- --spec tests/e2e/mcp-tool-labels.e2e.ts` —
passed; generated PNG verified at 2400x1600 and visually inspected
- `pnpm run test:e2e -- --spec tests/e2e/roadmap-thread-reopen.e2e.ts` —
passed at 2400x1600 after preserving the logical viewport

The `update-screenshots` label runs the complete screenshot suite and
publishes the normalized 2x PNGs through the repository's immutable
child-PR review workflow.

---------

Co-authored-by: Jonathan Kingston <KingstonMailBox@gmail.com>
Co-authored-by: copse-release-bot[bot] <304038887+copse-release-bot[bot]@users.noreply.github.com>
Co-authored-by: jonathanKingston <338988+jonathanKingston@users.noreply.github.com>
Lever 1 of 3 from a DevTools trace of a live agent turn. **This is the
biggest single win of the three.**

The set, biggest win first: **this PR** (~19 % of main-process work) ·
#2395 (~9 %, no dependency patch) · #2397 (~3 %, weakest).

> ⚠️ Conflicts with #2397 — both add `patchedDependencies` for the same
package. Whichever lands second needs a trivial rebase to merge the two
patch hunks.

## The problem

```js
export function whichSync(bin) {
    if (typeof globalThis.Bun !== 'undefined') return globalThis.Bun.which(bin);
    const result = spawnSync('which', [bin], { … });   // ← the Node/Electron path
```

`wrapCommandWithSandboxMacOS` (and its Linux counterpart) call
`whichSync(binShell || 'bash')` on **every sandboxed command**. Under
Bun that's an in-process `Bun.which`. Under Node it **forks
`/usr/bin/which`** — synchronously, on the Electron main thread.

From the trace:

| | |
| --- | --- |
| `whichSync` | **6.35 ms/call** — 72 % of the entire 8.85 ms sandbox
wrap |
| `spawnInProjectSandbox` | 56 calls in 5.5 s (~10/second) |

So Copse blocks its main thread with a fork/exec roughly ten times a
second, purely to look up a path that doesn't move.

**There is no way to avoid this through the package's API.** `whichSync`
is called unconditionally, and passing an absolute path doesn't help —
`which /bin/bash` still spawns a process.

## The change

A `pnpm patch` memoizing the Node fallback on `(binary, PATH)`. The Bun
branch is untouched. Measured standalone: **3.05 ms → 0.074 ms per call,
41×.**

**Only successful resolutions are cached** — see below for why that
matters.

## A real bug, caught by e2e (commit 2)

The first version of this patch cached misses too. That was wrong, and
e2e caught it:

```
tests/e2e/close-confirm.e2e.ts
  ✖ "before all" hook
  element (".prompt-input") still not existing after 30000ms
```

`spawnSync` carries `timeout: 1000`, so on a loaded machine `which` can
be killed before it answers and return null for a binary that is plainly
present. Upstream re-spawns next call and recovers. Caching that null
made **one transient timeout permanent for the life of the process** —
every later sandboxed command threw `Shell 'bash' not found in PATH`, so
the app could never reach a usable state.

It was this branch's bug, not CI's — the same shard passed on #2395 (no
dependency patch) and #2397 (patches this package but not `whichSync`),
and it failed on both attempts here.

Reproduced before fixing: look a binary up while absent, create it at
the same PATH, look again. Before, the second lookup still returned
null; after, it returns the path. A miss now costs one spawn next call,
exactly as upstream, and the hit path — the whole point of the memo — is
untouched.

Worth noting the unit tests could not have caught this on their own;
only e2e under real load did. That's an argument for the
`e2e`-on-ready-for-review gate doing its job.

## The patch is guarded by tests

`sandbox-which-cache.test.ts` pins both halves:

1. a resolved binary stays resolved after it is deleted (the memo exists
— fails if a version bump drops the patch),
2. **a failed lookup is not cached** (the regression above).

Both guards were verified to fail against the code they guard: (1)
against unpatched upstream, (2) against the previous commit's patch,
with `a failed lookup was cached`.

## On `pnpm patch-commit`

It rewrites `pnpm-workspace.yaml` — reordering `overrides`, **dropping
the `supportedArchitectures` block entirely**, and stripping every
explanatory comment. I reverted its output and hand-added just the
`patchedDependencies` block. Worth knowing before anyone runs it again.

## Safety

No change to permission policy, the platform matrix, or what the sandbox
allows or denies (`docs/shell-permissions.md`). The resolved shell path
is identical — it's just not re-derived per command.

The cache has no TTL, unlike #2395. `PATH` is part of the key and only
successes are cached, so the remaining exposure is "a binary was
replaced at the same PATH mid-session". Say the word if you'd rather it
were TTL-bounded for symmetry.

## Ideally this goes upstream

A small cache in `sandbox-runtime` itself would help every Node embedder
— including the miss-handling subtlety above — and the patch should be
dropped when it lands. I can't open that PR from here; this session is
scoped to `copse-dev/agent-pane`.

## Validation

- `tsc --noEmit -p tsconfig.node.json` — clean
- `eslint`, `oxfmt --check .` (2770 files) — clean
- `check-dead-code` — clean
- `project-sandbox` + `pnpm-workspace-invariants` — 41/41

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

https://claude.ai/code/session_01GdDtinDf7MGCnSuktjPM2M

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Lever 2 of 3 from a DevTools trace of a live agent turn. **This is the
only one of the three entirely in Copse's own code** — no dependency
patch, no carrying cost.

The set, biggest win first: #2396 (whichSync, ~19 % of main-process
work) · **this PR** (~9 %) · #2397 (~3 %, weakest). All three are
independent except #2396/#2397, which both patch the same dependency.

## What the trace showed

The Electron main process did **401 ms of real work in 5.8 s**,
essentially all of it in the sandboxed-spawn path.
`spawnInProjectSandbox` ran **56 times in 5.5 s (~10 sandboxed
processes/second)**.

`spawn.ts` rebuilds the whole filesystem rule set per spawn via
`workspaceSandboxOverlay`, and `canonicalizeWorkspaceRoot` runs a
blocking `realpathSync.native` on each rule path so seatbelt/bwrap rules
match the kernel's symlink-free view. `uncoveredSiblingDenyPaths`
re-resolves `homedir()` **plus every sibling worktree root**, every
single command.

| function | cost on the traced machine |
| --- | --- |
| `uncoveredSiblingDenyPaths` | 3.20 ms/command |
| `canonicalizeWorkspaceRoot` | 3.08 ms/command |

`realpath` blocks the Electron main thread, so this lands directly on
IPC latency — the trace shows it as part of 45–65 ms main-thread stalls
while tool calls fire.

## The change

Route `canonicalizeWorkspaceRoot` through a short-TTL, size-bounded memo
in a new `canonical-path-cache.ts`.

The cache is **deliberately time-bounded rather than permanent**.
Canonicalization is a filesystem fact and these rules are a security
boundary: a cached answer that outlived a symlink change would emit
rules naming the old target. A 2 s window collapses a burst of tool
calls into one syscall per path while keeping any staleness window far
shorter than the redundancy it removes. Size is bounded so a session
touching many projects/worktrees cannot grow it without limit.

Clock and resolver are injected at the boundary so the TTL and eviction
are testable without sleeping or touching the filesystem — no test-only
product API.

## Measured effect

Replaying the traced workload (56 spawns, 12-worktree project):

```
before: 0.2431 ms/spawn, 728 realpath syscalls
after : 0.0764 ms/spawn,  ~39 realpath syscalls   (~95% fewer)
```

## Rules are unchanged

The memo wraps the same `resolve` → `realpathSync.native` → fallback
sequence. Sibling-deny output is verified **byte-identical** before and
after. No change to permission policy, the platform matrix, or what the
sandbox allows or denies (`docs/shell-permissions.md`).

## Validation

- `tsc --noEmit -p tsconfig.node.json` — clean
- `eslint`, `oxfmt --check` — clean
- `check-dead-code` — clean (839 modules reachable)
- `node scripts/run-tests.mts project-sandbox/canonical-path-cache
project-sandbox/config` — **40/40 pass** (34 existing + 6 new)

`sandbox-fs-client` / `sandbox-fs-server` fail in my container, but they
fail identically on a clean tree — `node-pty` has no prebuilt Linux
binary and I installed with `--ignore-scripts`. Unrelated to this
change; please confirm they are green in CI.

## Not in scope

`canonicalizeReadTarget` (external read grants) has the same shape but
is a rarer path and did not appear hot in the trace. Left alone
deliberately.

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

https://claude.ai/code/session_01GdDtinDf7MGCnSuktjPM2M

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…age) and add shared isDefined/isNonNull (#2407)

Closes #1332.

Three mechanical passes, one commit each, in the order the issue
suggests.

### 1. `isDefined` / `isNonNull` — the highest-leverage addition

There was no shared presence predicate. 34 sites across 19 files wrote
one inline:

```ts
.filter((change): change is GitChange => change !== null)
```

TypeScript never checks that `change is GitChange` follows from the
body, so the annotation and the condition can drift apart silently, and
an anonymous predicate cannot be tested. Both helpers now live in
`packages/std/src/nullish.ts` — dependency-free, so the extracted
packages can use them too — re-exported as `@shared/nullish.ts` for app
code, with a test that pins the runtime behaviour *and* the narrowing
(`const nonNull: number[] = values.filter(isNonNull)` only compiles if
the predicate is right). Per `docs/type-safety.md`, the test was checked
against a `return true` body: all 6 go red.

The three call sites with an extra clause (`x !== undefined &&
x.trim().length > 0`) were left alone — they are not presence checks.

### 2. `isRecord` — 22 local copies deleted

The issue scoped this to "~6 in `src/**`, not 10", on the grounds that
the `packages/**` copies cannot import from `@shared`. That caveat is
now stale: `@copse/std` exists precisely as the dependency-free leaf
both sides share, and `hooks-dialects` / `plugin-sdk` already import
`isRecord` from it. So all 22 go, leaving one definition.

Two behavioural notes:

- **Five copies omitted `!Array.isArray(value)`** and so accepted arrays
as records (`stream-retry`, `spine-schema`, and three test files). Every
use of those five is `isRecord(v) && typeof v['field'] === '…'`, which
an array fails anyway — the canonical predicate is a narrowing with no
reachable behaviour change.
- `tool-args-format` (`!!value && …`) and `lm-studio-provider`
(reordered clauses) spelled the identical three conditions differently.

Also adds a short section to `docs/type-safety.md` pointing at the
shared predicates, so the next boundary parser reaches for one instead
of writing a 23rd.

### 3. `hasLastUsage` — five copies into `@copse/llm`

One was exported and tested in
`src/main/services/providers/provider-usage.ts`; three were verbatim
copies; `redacting-provider` had a variant narrowing `LLMProvider`
rather than `unknown`. Two live in extracted packages and so could not
import the tested one.

`LLMProvider` and `ModelUsage` both live in `@copse/llm`, so the
predicate moved there with its test. `ProviderWithUsage.lastUsage` is
now `ModelUsage | null` — the shape `llm-complete-text` already
declared, and a superset of the two-field shape the others used, so no
caller loses a field. `src/main/services/providers/provider-usage.ts` is
gone; its three importers take the package path.

### Out of scope

Step 4 of the issue ("revisit #1330's export question against whatever
is left") is a hand-off to #1330, which stays open.

### Verification

`pnpm run check` — typecheck (both projects), lint, format, dead-code,
oracle, e2e-syntax, demo-site, and the 8556-test unit suite (0
failures).

No visual eval: the four renderer files touched (`tool-args-format`,
`model-options`, `composer-editor`, `roadmap-pane`) are predicate
substitutions with identical results and unchanged DOM.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: copse-release-bot[bot] <304038887+copse-release-bot[bot]@users.noreply.github.com>
Co-authored-by: jonathanKingston <338988+jonathanKingston@users.noreply.github.com>
## The cancel

`Demo preview` runs keep ending `cancelled`, and it isn't the build
being superseded — it's the deploy.

`demo-preview.yml`'s `deploy` job was `uses:
./.github/workflows/pages.yml`. A called workflow's jobs run **inside
the caller's run**, so the deploy took the PR's own run into the shared
`pages` concurrency group. That group keeps a single pending slot, so a
newer deploy arriving while ours waited cancelled ours — and a cancelled
job marks the whole run cancelled, leaving a grey ✗ on the PR.

Across the last 40 `Demo preview` runs:

| conclusion | runs |
| --- | --- |
| cancelled | 23 |
| success | 14 |
| failure | 1 |

All 23 were on **different branches**, so none of them was same-branch
supersession — the per-branch `concurrency` group can't reach across
branches. Picking one apart ([run
34024334865](https://github.com/copse-dev/agent-pane/actions/runs/34024334865)):

```
publish        success
comment        success
deploy/deploy  cancelled   (created 09:21:45, cancelled 09:21:55 — never started)
→ run:         cancelled
```

The preview was built, published and commented. Only the queue slot was
lost. One push to `main` fans this workflow out into a dozen runs whose
deploys all reach the queue within seconds, so everything past the
deploying run and the one pending behind it is cancelled.

## The skip

**Dispatch `pages.yml` instead of calling it.** The supersession still
happens — `pages.yml`'s own note explains why `queue: max` was worse —
but it now cancels a standalone `Deploy site` run that no PR is
watching.

Nothing is lost by not waiting, for the same reason queued deploys were
interchangeable in the first place: the assembler reads whatever is on
the `demo-previews` tip, and `publish` has already pushed there.
Whichever deploy runs next carries this target — which is also why the
sticky `comment` job already didn't sit behind `needs: deploy`.

**And skip the dispatch outright when a deploy is already queued.** A
*queued* deploy hasn't assembled yet, so it will carry this build; a
second dispatch would only supersede it to publish the same tree.
Deliberately **not** "in progress" — such a deploy may have fetched the
tip before this push landed, so skipping on it would drop the preview.
Two runs can still race the check and both dispatch; the loser is
superseded on a run nobody watches, so this is an optimisation, never a
correctness condition. In a burst of a dozen pushes it collapses ~12
deploy runs to ~2.

**Warn rather than fail if the dispatch doesn't go out**, carrying over
the `tolerate-deploy-failure: true` the call used to pass. The concrete
case is a PR that merges while its preview is still building:
auto-delete takes the head branch with it and the dispatch ref 404s —
`ci.yml`'s autoformat job guards the same trap. Failing there would put
a red ✗ on an already-merged PR, which is worse than the grey one this
PR removes.

## Deliberately unchanged

- **`concurrency: cancel-in-progress: true`** on `demo-preview.yml`
stays. That cancel is the *right* one: it kills a build on a commit that
is no longer the head, saving several minutes of runner time on bytes
nobody will publish, and it never shows on the PR's current head.
Turning it into a skip would mean paying for the stale build and then
discarding it.
- **`demo-preview-cleanup.yml` keeps `workflow_call`.** Its deploy can
be cancelled the same way, but it fires on a *closed* PR where the
cancelled run is genuinely invisible — and keeping that caller keeps
`tolerate-deploy-failure` honest rather than dead config.

## Also

- Drops `pages: write` / `id-token: write` from `demo-preview.yml` —
nothing in it deploys any more, and the dispatched run mints those from
`pages.yml`'s own `permissions:` block. `actions: write` is declared on
the dispatching job alone, so `publish` (the one job running a shell
script against a write token) doesn't carry it.
- Updates the `pages.yml` concurrency note and the
`demo-links-per-pr-previews.md` plan, both of which documented the old
`workflow_call` choice explicitly.
- Four new pins in `ci-workflow-invariants.test.ts`: no `uses:` back
into `pages.yml`, the dispatch is on the pushed branch, the queued-only
guard never widens to `in_progress`, and the dispatch warns rather than
fails.

## Testing

`ci-workflow-invariants.test.ts` — 60/60 pass; `oxfmt --check` clean on
all four files. `npm run lint` and `typecheck` were not run here: the
container has no `node_modules` and the full install (~590 MB plus an
electron rebuild) isn't available, so CI is the first place they run.
The change is three YAML/Markdown files plus one test file that uses
nothing the file didn't already import.

The dispatch path itself can only be exercised on a real push to a
branch with an open PR, which this PR is — so `Demo preview` on this PR
is the test: it should end **green** with either `Dispatched the Pages
deploy on …` or `Deploy run … is already queued …` in the `deploy` job
log.

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

https://claude.ai/code/session_019JeFWg8CQL5JHK852n6YjX

---
_Generated by [Claude
Code](https://claude.ai/code/session_019JeFWg8CQL5JHK852n6YjX)_

Co-authored-by: Claude <noreply@anthropic.com>
…2397)

Lever 3 of 3 from a DevTools trace of a live agent turn.

> **This is the weakest of the three — land #2396 and #2395 first.** It
buys the least (~3 % of main-process work vs ~19 % and ~9 %), and it is
the only one that patches a security-critical dependency *and* cannot be
verified black-box. I'd understand a "not worth the carrying cost"
verdict; opening it because it was the third lever I measured.

> ⚠️ Conflicts with #2396 (lever 1) — both add `patchedDependencies` for
the same package. Whichever lands second needs a trivial rebase to merge
the two patch hunks.

## The problem

`generateSandboxProfile` pushes every read/write/deny entry through
`normalizePathForSandbox`, one blocking `realpathSync` apiece. A Copse
workspace overlay produces **~49 entries per sandboxed command**, and
**~35 % re-resolve a directory another entry already resolved** —
`<dir>` and `<dir>/**` are two entries naming one directory.

Trace: **2.94 ms per profile build**, once per command, ~10 times a
second.

## The change

Memoize those lookups **for one synchronous profile build**, cleared on
the next microtask.

The scoping is the whole point. These paths become seatbelt/bwrap rules,
so a resolution that outlived its build could emit a rule naming a stale
symlink target. A profile is generated synchronously, so the next
microtask is already the next build — nothing crosses that boundary. I
measured a cross-command cache too (92 % saving vs 37 %) and
**deliberately did not use it** for exactly this reason.

```
49 realpath calls → 32 per build
1.93 ms          → 1.22 ms   (37%)
```

## Why there is no behavioural test

The memo is **invisible in the return value by design**.
`isSymlinkOutsideBoundary` only accepts a resolution that is the path
itself, its `/private` canonical form, or a path beneath it — every
other resolution is discarded and the original spelling returned either
way. I verified this against the unpatched module across symlink, glob,
missing and plain-directory inputs: **outputs identical in every case.**

That is the safety property, but it also means no black-box test can see
the cache. So the guard pins what can actually regress:

1. the patch is present in the installed dependency,
2. the microtask clear is present (the security-relevant half),
3. normalization still produces the right answers.

Verified by reverting `node_modules` to upstream — the guard fails with
`patch missing from node_modules — a version bump likely dropped
patches/…`.

A source-string assertion is not lovely. It is the honest option here,
and it is what would actually catch a version bump silently dropping the
patch.

## Also worth knowing

`pnpm patch-commit` rewrites `pnpm-workspace.yaml` — reordering
`overrides`, dropping the `supportedArchitectures` block, and stripping
every comment. I reverted its output and hand-added only the
`patchedDependencies` block.

## Safety

No change to permission policy, the platform matrix, or what the sandbox
allows or denies (`docs/shell-permissions.md`).

## Validation

- `tsc --noEmit -p tsconfig.node.json` — clean
- `eslint`, `oxfmt --check .` (2770 files) — clean
- `check-dead-code` — clean
- `project-sandbox` + `pnpm-workspace-invariants` — **41/41**, plus the
negative test above

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

https://claude.ai/code/session_01GdDtinDf7MGCnSuktjPM2M

---
_Generated by [Claude
Code](https://claude.ai/code/session_01GdDtinDf7MGCnSuktjPM2M)_

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Retires `src/shared/types/ipc.ts`. The hand-written `IpcInvokeMap` /
`IpcEventMap` were a second, drifting description of a surface that
#2352 already froze as a generated protocol.

## Why it is dead

- **Nothing imported it.** Those two interfaces were its only exports.
They were re-exported through the `@shared/types` barrel, but no file in
the repo names either type.
- **Its private types were dead too.** `Provider`, `CloudProvider` and
`AvailableProviders` were never exported at all, and the renderer
already derives its own `AvailableProviders` from the real contract
(`ApiClient['settings']['availableProviders']`,
`src/renderer/views/model-options.ts`).
- **It was a partial mirror, and drifting.** It named 149 channels
against the protocol's 241 invoke + 2 send + 54 event. Coverage had
*decreased* since #2352 measured 111/237 and 36/53, because new channels
land in `api.d.ts` and nobody updates this file.
- **It was already on the books to go.** #2352 explicitly rejected it as
a generator source (rationale in `scripts/lib/api-protocol.mts`), and
`docs/api-protocol.md` listed retiring it under "Known gaps".

The source of truth is unchanged: `ApiClient` in `src/preload/api.d.ts`
plus the preload bindings, generated into
`schemas/api-protocol.manifest.json`.

## What changed

- Deleted `src/shared/types/ipc.ts` and its `export *` line in
`src/shared/types/index.ts`.
- Removed the invariants test `ipc.ts only names channels the protocol
has`. That guard existed solely to stop the stale maps naming channels
that no longer exist "until it is retired" — it has no subject now.
- Updated the generator's rationale comment, the "Known gaps" section of
`docs/api-protocol.md`, and the step-1 findings in
`docs/plans/client-server-split.md`.

## Notes for a reviewer

- **The generated manifest is byte-identical.** `ipc.ts` was never a
generator input, so the protocol surface does not move and no version
bump applies. The manifest drift test passing is the proof.
- **Type-only deletion.** The file contained no runtime exports, so the
compiled barrel loses nothing.
- Three older plan docs still mention `ipc.ts` (`thread-referencing.md`,
`managed-agent-environments.md`, `ssh-remote-repo.md` — the last with
already-stale line numbers). They read as point-in-time snapshots of
unrelated plans rather than live pointers, so they are left alone; happy
to scrub them if preferred.

## Verification

`typecheck`, `lint`, `format:check` and `check:dead-code` (838 modules
reachable) all pass. `pnpm test api-protocol` is 19/19.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
)

Closes #1330.

**183 hand-written predicates → 118**, and a ratchet so the count can
only go one way. Six commits plus a merge, in the order the issue lays
the work out.

> Rebased onto #2407, which landed while this was open. The two do not
overlap by design — that PR took the 26 presence checks this one
deliberately left alone; this one took the membership and narrowing
ones. All six conflicts were import lines and one docs section, no
logic. Base figures below are against `main` **with** #2407.

## 1. The blocker isn't one

#1330 says the remedy is unavailable until
`@typescript-eslint/explicit-function-return-type` is exempted, because
obtaining inference means deleting the return annotation. Measured
against this repo's config, in both directions:

| | lints? |
| --- | --- |
| `function isFoo(v: unknown) { return typeof v === 'string' }` | ✗
`explicit-function-return-type` **and** `explicit-module-boundary-types`
|
| `const isFoo: (v: unknown) => v is Foo = (v) => typeof v === 'string'`
| ✓ clean |
| `xs.filter((x) => typeof x === 'string')` | ✓ clean |

So only the route the issue proposed violates the rule. Moving the
annotation to the **binding** keeps the signature written down and gets
the check for free — `allowTypedFunctionExpressions` is on by default —
and an unannotated arrow in argument position never triggered the rule
at all.

The annotated form is also the *better* remedy, not just the available
one. Inference by deletion degrades silently: a body that stops
narrowing yields plain `boolean` and the caller quietly gets a wider
type. The annotated form is `TS2677` instead — `return true` fails to
compile, which is exactly the case the issue opens with.

**No lint config changes in this PR.**

## 2. Membership: 28 assertions → 1

`.includes()` / `.some()` / `.has()` don't narrow, so these can be
neither checked nor inferred — the issue's "at least 14 that genuinely
need the assertion". They don't; they need to stop being written by
hand.

```ts
export const isThemePreference = memberOf(THEME_PREFERENCES)
```

`memberOf` (`packages/std/src/member-of.ts`) holds the codebase's single
membership `is` assertion. Its test checks the exact contract —
`memberOf(list)(value) === list.includes(value)` — over a cross-product
of 12 member lists and a hostile corpus (prototype keys, near-misses,
symbols, bigints, `NaN`, functions with a matching `toString`), plus
compile-time narrowing assertions. Per `docs/type-safety.md` I checked
it can fail: a `return true` body turns **6 of its 10 cases red**.

Two things fell out:

- **`decision-log.ts` and `deferred-approval.ts` spelled their member
lists twice** — once as a union, once as a `Set` literal — so the
predicate could silently disagree with the type it claimed. The list is
now the source and the type derives from it, which makes that drift
unrepresentable rather than merely tested.
- Three sites keep an explicit annotation because their declared input
is narrower than `unknown` (`isCursorPermissionHookEvent`,
`isServiceTier`, `isHookDialect`). The factory's return type is checked
against it.

## 3. Convert what's cheap — measured, not guessed

I converted **all 100** function-declaration predicates to the annotated
form and kept only what `tsc` accepted: **18**.

That is the honest headline, and it is much less than the issue
anticipates. What survives is `instanceof`, `Array.isArray`, `in` over
an object union, a discriminant comparison, and a literal-union
disjunction. What doesn't is the bulk of this codebase's predicates:
**structural boundary parsers**, where indexed access doesn't narrow the
object. `isRecord` fails twice over — the negated `Array.isArray` stops
inference producing a predicate at all, and even without it the most the
compiler concludes is `v is object`, which has no index signature. There
is no way to write it that the compiler checks.

For the inline `.filter((x): x is T => …)` arrows — the one shape that
can *never* satisfy the "exported predicates must be tested" rule,
because an anonymous predicate has no test surface — I stripped all 73
non-presence annotations and **diffed the resolved type of every call
expression in every touched file**. 24 had moved:

- `Boolean(part)` doesn't narrow at all — `(string | undefined)[]`, not
`string[]`;
- in `cursor-adapter.ts` two filters lost their `wireEvent` narrowing
and started reporting a *different* event set, still compiling;
- where the input array is `any[]` (a `storageGet` read), the annotation
was the only thing pinning the element type, and removing it trips
`no-unsafe-*`.

So the batch that landed is the 21 sites where **nothing moves**: every
call-expression type across the 17 files is byte-identical before and
after. The 52 excluded sites keep their annotation and stay in the
inventory.

## 4. The ratchet

The follow-up comment on #1330 is the real finding: 161 → 212 in four
months, with 15 covered. Counting by hand can't keep up, and nothing in
the build objects — `no-unsafe-type-assertion` doesn't flag a predicate,
and the suppression baseline is empty, so an unverified `x is T` passes
every gate we have.

`scripts/type-predicate-inventory.test.ts` lists the 118 that remain and
fails **both ways**, the same shape as `module-boundaries.test.ts`: a
predicate not on the list fails, and a list entry whose predicate is
gone fails. Converting one forces its line out in the same change, so
the list can only shrink. I verified both directions by adding a
predicate and by removing one.

Only the **asserted** form is counted — `memberOf(TUPLE)`, an annotated
binding and an unannotated `.filter()` arrow are all absent by
construction, so the cheapest predicate to add is now also the honest
one. That classification is load-bearing, so
`scripts/lib/type-predicates.mts` has its own unit test for the ten
shapes it has to tell apart.

`memberOf`'s own assertion is *in* the list, as are `isDefined` /
`isNonNull` from #2407. That is the point: the shared ones are the
audited ones, and the list says which they are.

## 5. Docs

`docs/type-safety.md` gains the three checked forms, the lint finding
above, and a measured table of **what TypeScript can and cannot infer a
predicate from** — including that a preceding `const` is fine but an
early `return false` is not, and the two traps that make a blanket
conversion unsafe. The point is that nobody re-derives this. `memberOf`
is folded into #2407's "reach for the shared predicate" list rather than
competing with it.

## Numbers

Counted by `scripts/lib/type-predicates.mts` over tracked non-test
`src/` + `packages/` + `scripts/`, which is why the base is 183 rather
than the issue's 212.

| | main (incl. #2407) | here |
| --- | --- | --- |
| asserted predicates | 183 | **118** |
| files carrying one | 113 | 74 |
| membership predicates written by hand | 28 | 0 |

The remaining 118 are overwhelmingly structural boundary parsers.
Driving those to zero is a decoder-at-the-boundary job, not a predicate
job — `docs/type-safety.md` now says so and points at
`safeJsonParse(text, decodeWithSchema(schema))`.

## Verification

`pnpm run check` end to end on the merged tree: typecheck (both
projects), lint, format, demo-site, dead-code, oracle, e2e-syntax, and
**8,587 passing / 0 failing** unit tests.

No visual eval. The seven renderer files touched are predicate-form
substitutions with identical runtime semantics and unchanged DOM —
`main.ts`'s `Set.has` and `memberOf`'s internal `Set.has` are the same
call, and every other change is type-level.

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

https://claude.ai/code/session_01J9mJfbsrvEeTMUdxTKf8xZ

---------

Co-authored-by: Claude <noreply@anthropic.com>
@copse-release-bot
copse-release-bot Bot merged commit 26b145b into release Sep 6, 2026
53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant