Repository navigation
feat(desktop): Windows support - #9499
waleedlatif1 wants to merge 72 commits into
Conversation
- electron-builder: per-user one-click NSIS target with per-channel .ico, Authenticode for native addons, and platform-scoped node-pty prebuilds; the Computer Use helper moves under mac. - node-pty win32 prebuilds pinned beside the darwin ones; ensure-pty-prebuilds fetches per host platform (ConPTY binaries on Windows) with Windows' own tar. - build-native skips on Windows; install:local and package:share refuse to run off macOS. - Committed icon*.ico generated from the per-channel dock icons by scripts/generate-windows-icons.ts; build.ts selects one per baked origin.
…NSIS updater A per-platform descriptor picks the manifest (latest-mac.yml / latest.yml), the exact release artifacts a feed may offer, and whether a native stage follows the download. Windows has none: electron-updater's update-downloaded fires after the installer's publisher signature is verified, so it drives ready directly, and the confirmed restart installs silently and relaunches. Self-update on Windows requires the packaged app-update.yml; otherwise the manual-download pipeline offers the installer.
…tform The bridge now carries an optional platform (absent shells are macOS). One per-platform feature table drives the preload surfaces, the IPC table, the background runner, and device registration, so Windows does not advertise or accept the terminal, native file tools, computer use, terminal themes, Chrome import, or password copy. The web app feature-detects the terminal, folder grants, and password copy from the bridge, sends the platform with chat capabilities, labels the tray setting for Windows, and keys the title-bar lane on the bridge platform instead of the user agent.
The macOS app-menu shape (hide/hideOthers/unhide, a windowMenu whose Close item takes Ctrl+W off macOS) is wrong on Windows. Build a Windows template with the same items and accelerators, Exit via role quit, and hidden Alt+Left/Alt+Right for history. The macOS template is unchanged.
A template image is invisible on a dark Windows taskbar. Use the per-channel app icon with 1.25x/1.5x/2x representations, open Sim on a left-click (Windows only shows the menu on right-click), and flash the taskbar button when a download finishes in a background window.
…w chrome A Windows relaunch arrives as second-instance and now surfaces the running window instead of opening another. Claim the installer's AppUserModelID, auto-hide the menu bar on native-frame windows, drop the menu from frameless sheets, keep Sim resident in the tray when the last window closes, and lift handoff-focused windows past Windows' foreground lock. The native caption follows Sim's theme preference when the renderer reports one.
…enu on Windows The preload sends the explicit sim-theme preference alongside the resolved theme so main can follow an explicit choice on Windows. The credential picker drops the app menu Windows attaches to every window, so its accelerators cannot act inside the picker.
- update-feed: a per-platform descriptor (manifest, artifact names, installer preference). Windows builds publish independently, so a release without latest.yml is not a Windows candidate (404 no-release, never a 502), while a latest.yml naming anything but Sim-<v>-x64.exe rejects the release. - New /api/desktop/update/latest.yml twin of latest-mac.yml, sharing one handler; the GitHub resolution moves into resolveDeploymentRelease. - /api/desktop/update/download takes ?platform=mac|windows through a contract (default mac for shells that predate it); the update gate passes the shell's platform.
sim-setup desktop resolves the installer and feed for the host platform, and the desktop and self-hosting docs cover the Windows installer, requirements, endpoints, and the features Windows does not have yet.
Windows has no process groups, so teardown kills the app's tree with taskkill /T before removal and retries removal while files unlock.
…lesystem A model-supplied native path is now resolved without any fs call on Windows: only drive-absolute and ~ paths pass, so a UNC path can no longer make the shell authenticate to a remote host before consent, and device, verbatim, drive-relative, and rooted paths are refused. Path segments in native paths and localfs URIs must be plain NTFS names (no streams, reserved devices, trailing dots or spaces, or invalid characters). File identity checks compare bigint dev/ino, the missing Windows O_NOFOLLOW is explicit, and the atomic store write retries a rename that Defender or the indexer briefly blocks.
- desktop-release takes a platform input. The new build-windows job resolves the same channel identity (plus the package-name slug), signs through Azure Artifact Signing after an OIDC azure/login, and validates the exact Sim-<v>-x64.exe, its blockmap, latest.yml (version, url, path, sha512), each channel's updater cache, Authenticode on the installer and app exe, and the publisherName pinned in app-update.yml. Then it runs the packaged smoke and uploads with the manifest last. - ci: a separate Azure signing probe gates the Windows stable and prerelease calls, which skip with a warning until configured. Nothing waits on them, so macOS publication never depends on the Windows build. - desktop-e2e: a windows-latest leg runs the desktop unit suite and the packaged smoke against an unsigned unpacked x64 build.
Regenerating it inlines the bridge's relative imports (./local-files, ./title-bar, ./computer-use.generated), which the check cannot resolve. The platform changes are additive and pass the check against the existing snapshot.
… browser The agent's clipboard guard also refuses Blink's Shift+Insert, Ctrl+Insert, and Shift+Delete bindings off macOS, and a Cmd editing shortcut becomes Ctrl there instead of the Windows key. Staged uploads and downloads get NTFS-safe names on Windows. Password copy refuses in main as well as at the bridge, and the credential storage comments describe DPAPI and the confirmation fallback alongside Keychain and Touch ID.
Gate computer use through the platform feature table, guard the now-optional terminal bridge in the e2e specs, and name the Windows File/Help menus in the self-hosting docs and sim-setup output.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Exclude the suites for subsystems Windows builds do not ship, keep POSIX mode-bit assertions to POSIX, and build expected paths with path.join.
[Medium impact] Windows support reaches the desktop app, its update feed, and its release workflows. Fix the closed-terminal path before merging so Chat cannot report a command that never ran. Findings
SummaryAdds Windows desktop packaging, signing, menus, tray behavior, file access, and updates. The latest changes tighten terminal startup and Stop behavior.
Reviews (3) · Last reviewed commit: "fix(desktop): type the agent's next comm..." · Reviewed by Greptile |
electron-builder's dependency collector fails on a missing production dependency but skips a missing optional one, and bun installs only the host platform's prebuild, so each OS was failing on the other's packages.
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Confidence score: 3/5
- On
/andapp/(landing)pages,themePreference()inapps/desktop/src/preload/shell-theme.tsreadssim-themeinstead ofsim-landing-theme, so it can report a preference that doesn’t match the landing page theme. Read the landing-theme key for those routes.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/preload/shell-theme.ts">
<violation number="1" location="apps/desktop/src/preload/shell-theme.ts:20">
P2: `themePreference()` always reads the app's `sim-theme` key, but marketing pages (`/` and `app/(landing)` segments) theme from `sim-landing-theme`. On those pages the reported preference is from the wrong store, so the Windows caption and `nativeTheme.themeSource` follow the app choice while the page shows the landing toggle's theme. Read `sim-landing-theme` (default `light`) when the path is a landing path, mirroring `isLandingPath` in `apps/sim/lib/landing/routes.ts`.</violation>
</file>
This PR changes authentication, authorization, or input validation. Ultrareviews find 2.4x more serious bugs than standard reviews. Comment @cubic-dev-ai ultrareview to run one.
Turn on auto-fix | Re-trigger cubic
GitHub validates every job of a reusable workflow against the calling job's permissions at startup, so the new id-token: write request in desktop-release made the whole ci workflow fail to start.
isTerminalEnabled now requires the bridge to expose a terminal, which a Windows shell omits.
CHANNEL_IDENTITIES (src/shared/channel-identity.ts) holds each channel's name, app ID, and icons. The packager's channels.ts adds only the origin, install-local reuses it, and the running app reads its name, AppUserModelID, and icons from the same entries, so the runtime identity can no longer drift from the installer's.
…er-platform layouts buildMenuTemplate assembles the items both platforms share once and hands them to buildWindowsMenu or buildMacMenu; each menu's contents are unchanged.
…the feed's select feedErrorResponse answers both the manifest feed and the download redirect when no release can be offered, and resolveDeploymentRelease returns the release's assets for each route to read its own field.
addRepresentation decodes a PNG buffer directly, so each scale skips the base64 data URL round trip.
…d loop The array already puts latest.yml last, so one loop keeps the manifest after the installer it names.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
…suite Browser import does not ship on Windows; its platform gate stays covered by the import-service tests, which inject the platform.
There was a problem hiding this comment.
Review completed against the latest diff
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
Stop selected every direct child of the shell, so a server the user had started in the background earlier in the same terminal was killed with the command. The session records when the agent's command was typed, and on Windows only children created since then are its processes. The Windows suite starts a background child before the stubborn command and checks it survives Stop.
…oss chunks While the query is pending, a chunk's tail that could begin it is held until the next chunk, which either completes the query or releases it.
cmd escapes with a caret, literal inside double quotes, and has no single quotes; PowerShell escapes with a backtick.
Git for Windows' bin\bash.exe holds the pty and starts the real bash, so the shell's direct children predate every command and Stop found nothing to end. Stop now walks the shell's descendants and takes the outermost ones created since the command was typed.
…rawn Git Bash drops input typed while it is still switching from a native program back to its prompt, so a command run straight after a stopped one vanished. bash now reports its prompt's end (OSC 633 B, drawn by readline at the end of PS1), as PowerShell already did, and a run into a shell that reports it waits for the prompt, at most 2 s. The bash init file also sets PROMPT_COMMAND before its DEBUG trap, so that assignment is no longer announced as a command at startup.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
| if (!session.hasShellIntegration) { | ||
| await this.awaitShellStartup(session, latch) | ||
| } | ||
| await session.waitForPromptDrawn(latch.signal) |
There was a problem hiding this comment.
If the user closes the terminal or the shell exits while waitForPromptDrawn() waits, closing resolves the wait and clears the busy state without aborting the call. run() then calls runCommand() on the dead session. Its writes do nothing, but its timer eventually returns status: 'running' even though nothing ran.
Check session.alive after this await and return SESSION_CLOSED before starting the command.
| await session.waitForPromptDrawn(latch.signal) | |
| await session.waitForPromptDrawn(latch.signal) | |
| if (!session.alive) { | |
| throw new TerminalError('SESSION_CLOSED', 'The shell closed before the command could start.') | |
| } |
There was a problem hiding this comment.
7 issues found across 125 files
Confidence score: 3/5
process-group.tscan force-kill an unrelated Windows process if the command exits and its PID is reused beforetaskkillruns. Retain and revalidate the process creation identity before stopping it.index.tscan report a command as running after the shell exits during the wait, even though the command never started. Checksession.aliveafter the wait and returnSESSION_CLOSED.describeRunningCommandinpackages/terminal-protocol/src/index.tssplits on whitespace before removing quotes, so a quoted Windows executable path with spaces can be misidentified. Parse the quoted path before splitting it.shell-integration.tscan use a stale$LASTEXITCODEfor a later failing cmdlet. Track whether the accepted command updated it before using it to report the result.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/src/main/terminal/session.ts">
<violation number="1" location="apps/desktop/src/main/terminal/session.ts:626">
P2: `waitForPromptDrawn` keeps waiting while a command is running, so a `terminal_run` against a busy bash or PowerShell terminal stalls about 2 seconds before it reports BUSY. Resolve the wait when a command already holds the foreground.</violation>
<violation number="2" location="apps/desktop/src/main/terminal/session.ts:713">
P2: The `interrupted` flag is only set by `kill('SIGINT')`, so a Ctrl-C typed in the panel or sent with `terminal_input` does not set it. Set the flag where the `\u0003` byte is written so every Ctrl-C path is covered.</violation>
</file>
<file name="packages/terminal-protocol/src/index.ts">
<violation number="1" location="packages/terminal-protocol/src/index.ts:421">
P2: `describeRunningCommand` splits the shell line on whitespace before stripping quotes, so a quoted Windows path with spaces is cut in half. `& "C:\Program Files\nodejs\npx.cmd" create-app` under `pwsh` is labelled `Program`, and that label reaches the close dialog and tab title. Tokenize the segment quote-aware instead of using `split(/\s+/)`, and add a test for a quoted path with spaces.</violation>
<violation number="2" location="packages/terminal-protocol/src/index.ts:440">
P3: The new helper strips `.exe` from POSIX command labels too, changing macOS behavior for valid executables such as `/usr/local/bin/foo.exe`. Strip the extension only for Windows syntax while keeping `programName`'s Windows normalization.</violation>
</file>
<file name="apps/desktop/src/main/terminal/shell-integration.ts">
<violation number="1" location="apps/desktop/src/main/terminal/shell-integration.ts:408">
P2: This reports a stale native exit code for later failing cmdlets. Track whether the accepted command updated `$LASTEXITCODE` (or otherwise distinguish native failures) before using it; otherwise a cmdlet failure can be attributed the wrong exit status.</violation>
</file>
<file name="apps/desktop/src/main/terminal/process-group.ts">
<violation number="1" location="apps/desktop/src/main/terminal/process-group.ts:101">
P1: Windows Stop loses the process creation identity before invoking `taskkill`. If a command exits and Windows reuses its PID during this race, Stop can force-kill an unrelated process; retain and revalidate `(pid, createdAt)` immediately before killing the tree.</violation>
</file>
<file name="apps/desktop/src/main/terminal/index.ts">
<violation number="1" location="apps/desktop/src/main/terminal/index.ts:1393">
P2: Check `session.alive` after this wait and return `SESSION_CLOSED` before starting the command; otherwise a shell exit can make `run()` report a command as running even though it never ran.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
| commandRoots(await readProcesses(), shellPid, startedAt), | ||
| stillRunning: async (_shellPid, pids) => pids.filter(isRunning), | ||
| signal: (pids) => { | ||
| for (const pid of pids) killProcessTree(pid) |
There was a problem hiding this comment.
P1: Windows Stop loses the process creation identity before invoking taskkill. If a command exits and Windows reuses its PID during this race, Stop can force-kill an unrelated process; retain and revalidate (pid, createdAt) immediately before killing the tree.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/desktop/src/main/terminal/process-group.ts, line 101:
<comment>Windows Stop loses the process creation identity before invoking `taskkill`. If a command exits and Windows reuses its PID during this race, Stop can force-kill an unrelated process; retain and revalidate `(pid, createdAt)` immediately before killing the tree.</comment>
<file context>
@@ -1,34 +1,193 @@
+ commandRoots(await readProcesses(), shellPid, startedAt),
+ stillRunning: async (_shellPid, pids) => pids.filter(isRunning),
+ signal: (pids) => {
+ for (const pid of pids) killProcessTree(pid)
+ },
+ escalations: ['SIGTERM'],
</file context>
| // SIGINT is delivered as a keystroke so the foreground process group gets | ||
| // it the way Ctrl-C would, rather than only the shell. | ||
| if (signal === 'SIGINT') { | ||
| if (this.pendingCommand) this.pendingCommand.interrupted = true |
There was a problem hiding this comment.
P2: The interrupted flag is only set by kill('SIGINT'), so a Ctrl-C typed in the panel or sent with terminal_input does not set it. Set the flag where the \u0003 byte is written so every Ctrl-C path is covered.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/desktop/src/main/terminal/session.ts, line 713:
<comment>The `interrupted` flag is only set by `kill('SIGINT')`, so a Ctrl-C typed in the panel or sent with `terminal_input` does not set it. Set the flag where the `\u0003` byte is written so every Ctrl-C path is covered.</comment>
<file context>
@@ -639,6 +710,7 @@ export class TerminalSession {
// SIGINT is delivered as a keystroke so the foreground process group gets
// it the way Ctrl-C would, rather than only the shell.
if (signal === 'SIGINT') {
+ if (this.pendingCommand) this.pendingCommand.interrupted = true
this.write('\u0003')
return
</file context>
| resolve() | ||
| } | ||
| const check = () => { | ||
| if (this.promptDrawn || !this.reportsPromptEnd || this.disposed) done() |
There was a problem hiding this comment.
P2: waitForPromptDrawn keeps waiting while a command is running, so a terminal_run against a busy bash or PowerShell terminal stalls about 2 seconds before it reports BUSY. Resolve the wait when a command already holds the foreground.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/desktop/src/main/terminal/session.ts, line 626:
<comment>`waitForPromptDrawn` keeps waiting while a command is running, so a `terminal_run` against a busy bash or PowerShell terminal stalls about 2 seconds before it reports BUSY. Resolve the wait when a command already holds the foreground.</comment>
<file context>
@@ -561,6 +607,31 @@ export class TerminalSession {
+ resolve()
+ }
+ const check = () => {
+ if (this.promptDrawn || !this.reportsPromptEnd || this.disposed) done()
+ }
+ timer = setTimeout(done, PROMPT_DRAW_MAX_MS)
</file context>
| if (this.promptDrawn || !this.reportsPromptEnd || this.disposed) done() | |
| if (this.promptDrawn || !this.reportsPromptEnd || this.disposed || this.foregroundCommand !== null) done() |
| .split('/') | ||
| .filter(Boolean) | ||
| .pop() ?? '') | ||
| ? lastPathSegment(program.replace(/^['"]|['"]$/g, ''), syntax.windowsPaths ? /[\\/]/ : '/') |
There was a problem hiding this comment.
P2: describeRunningCommand splits the shell line on whitespace before stripping quotes, so a quoted Windows path with spaces is cut in half. & "C:\Program Files\nodejs\npx.cmd" create-app under pwsh is labelled Program, and that label reaches the close dialog and tab title. Tokenize the segment quote-aware instead of using split(/\s+/), and add a test for a quoted path with spaces.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/terminal-protocol/src/index.ts, line 421:
<comment>`describeRunningCommand` splits the shell line on whitespace before stripping quotes, so a quoted Windows path with spaces is cut in half. `& "C:\Program Files\nodejs\npx.cmd" create-app` under `pwsh` is labelled `Program`, and that label reaches the close dialog and tab title. Tokenize the segment quote-aware instead of using `split(/\s+/)`, and add a test for a quoted path with spaces.</comment>
<file context>
@@ -371,15 +418,29 @@ export function describeRunningCommand(running: string): string {
- .split('/')
- .filter(Boolean)
- .pop() ?? '')
+ ? lastPathSegment(program.replace(/^['"]|['"]$/g, ''), syntax.windowsPaths ? /[\\/]/ : '/')
: '') || segment
return truncate(label, MAX_RUNNING_COMMAND_LABEL, '…')
</file context>
| } | ||
| if ($Global:__SimInCommand) { | ||
| $Global:__SimInCommand = $false | ||
| $exitCode = if ($succeeded) { 0 } elseif ($nativeExitCode -is [int] -and $nativeExitCode -ne 0) { $nativeExitCode } else { 1 } |
There was a problem hiding this comment.
P2: This reports a stale native exit code for later failing cmdlets. Track whether the accepted command updated $LASTEXITCODE (or otherwise distinguish native failures) before using it; otherwise a cmdlet failure can be attributed the wrong exit status.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/desktop/src/main/terminal/shell-integration.ts, line 408:
<comment>This reports a stale native exit code for later failing cmdlets. Track whether the accepted command updated `$LASTEXITCODE` (or otherwise distinguish native failures) before using it; otherwise a cmdlet failure can be attributed the wrong exit status.</comment>
<file context>
@@ -279,13 +314,115 @@ __sim_precmd() {
+ }
+ if ($Global:__SimInCommand) {
+ $Global:__SimInCommand = $false
+ $exitCode = if ($succeeded) { 0 } elseif ($nativeExitCode -is [int] -and $nativeExitCode -ne 0) { $nativeExitCode } else { 1 }
+ $markers += (__Sim-Osc "D;$exitCode")
+ }
</file context>
| if (!session.hasShellIntegration) { | ||
| await this.awaitShellStartup(session, latch) | ||
| } | ||
| await session.waitForPromptDrawn(latch.signal) |
There was a problem hiding this comment.
P2: Check session.alive after this wait and return SESSION_CLOSED before starting the command; otherwise a shell exit can make run() report a command as running even though it never ran.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/desktop/src/main/terminal/index.ts, line 1393:
<comment>Check `session.alive` after this wait and return `SESSION_CLOSED` before starting the command; otherwise a shell exit can make `run()` report a command as running even though it never ran.</comment>
<file context>
@@ -1394,6 +1390,7 @@ export class TerminalService {
if (!session.hasShellIntegration) {
await this.awaitShellStartup(session, latch)
}
+ await session.waitForPromptDrawn(latch.signal)
if (session.isBusy) {
throw new TerminalError(
</file context>
| await session.waitForPromptDrawn(latch.signal) | |
| await session.waitForPromptDrawn(latch.signal) | |
| if (!session.alive) { | |
| throw new TerminalError('SESSION_CLOSED', 'The terminal session closed before the command started.') | |
| } |
| .split(separator) | ||
| .filter(Boolean) | ||
| .pop() | ||
| ?.replace(/\.exe$/i, '') ?? '' |
There was a problem hiding this comment.
P3: The new helper strips .exe from POSIX command labels too, changing macOS behavior for valid executables such as /usr/local/bin/foo.exe. Strip the extension only for Windows syntax while keeping programName's Windows normalization.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/terminal-protocol/src/index.ts, line 440:
<comment>The new helper strips `.exe` from POSIX command labels too, changing macOS behavior for valid executables such as `/usr/local/bin/foo.exe`. Strip the extension only for Windows syntax while keeping `programName`'s Windows normalization.</comment>
<file context>
@@ -371,15 +418,29 @@ export function describeRunningCommand(running: string): string {
+ .split(separator)
+ .filter(Boolean)
+ .pop()
+ ?.replace(/\.exe$/i, '') ?? ''
+ )
+}
</file context>
Summary
Adds Windows (x64, NSIS) support to the desktop app. The macOS behaviour is unchanged.
Shell
Feature gating
platformfield on the bridge (optional; older shells are all macOS).src/shared/platform-features.ts) drives the preload surface, IPC registration, executor registration and the background runner.user-local/VFS grants stay on.Security
\\?\and\\.\paths, drive-relative and rooted paths, reserved device names, alternate data streams, and trailing dots or spaces. It closes a pre-consent SMB/NTLM leak viarealpath.Packaging and updates
extraMetadata.name), multi-size.icoper channel, and signing of the.nodefiles.readyfrom electron-updater, since there is no Squirrel step on Windows./api/desktop/update/latest.yml. A release without a Windows manifest means "no update", not 502./api/desktop/update/download?platform=windows.CI
publisherNamechecks, and the packaged smoke test.package-smoke-windowsleg indesktop-e2e.ymlthat runs the desktop unit suite and the packaged smoke test.Rollout
No Windows artifacts are published until the Azure signing secrets and variables exist (see the README table). Until then the probe skips the job with a warning, so the macOS releases are unaffected.
Test plan