revert(daemon): ship official Node in the macOS terminal host again, keep stall telemetry (#33688)

Reverts #29133 except its daemon async-I/O stall telemetry, so we can measure how often stalls happen on official Node before committing to a patched runtime. The macOS terminal host goes back to the official Node 24.22.1 download and the daemon protocol back to v51; no release or hourly build published v52.
This commit is contained in:
Jinwoo Hong
2026-10-25 20:07:30 -04:00
committed by GitHub
parent 5a4dd30048
commit a5f7da4928
35 changed files with 87 additions and 1594 deletions
+9 -17
View File
@@ -267,8 +267,8 @@ jobs:
matrix:
arch: [x64, arm64]
runs-on: blacksmith-12vcpu-macos-15
# Allow 360min: 180 for a cold patched-runtime build + 150 for install/publish retries and setup.
timeout-minutes: 360
# Allow 150min for install 3×10 + publish 2×45 + ~25 setup/build/verify; outer timeout would prevent cleanup.
timeout-minutes: 150
env:
JOBS: max
NODE_OPTIONS: --max-old-space-size=4096
@@ -308,7 +308,7 @@ jobs:
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Cache the patched host Node by exact pin and patch, without restore keys.
# Cache bundled host Node by exact pin, without restore keys, so bumps cannot restore old archives.
- name: Locate the pinned Node runtime cache
shell: bash
run: echo "ORCA_NODE_RUNTIME_CACHE_DIR=$HOME/Library/Caches/orca-node-runtime" >>"$GITHUB_ENV"
@@ -317,7 +317,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/Library/Caches/orca-node-runtime
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts', 'config/node-runtime-patches/**', 'config/scripts/build-darwin-terminal-node.mjs', 'config/scripts/pinned-node-downloads.mjs', 'config/scripts/update-node-runtime-pin.mjs', 'config/scripts/macos-terminal-host-bundle.cjs') }}
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts') }}
- name: Cache electron-builder downloads
uses: actions/cache@v5
@@ -338,15 +338,6 @@ jobs:
retry_wait_seconds: 30
command: pnpm install --frozen-lockfile --cpu=current,x64,arm64
# Why: older refs predate the source build and package the pinned download instead.
- name: Build and verify the patched terminal runtimes
if: hashFiles('config/scripts/build-darwin-terminal-node.mjs') != ''
timeout-minutes: 180
run: |
pnpm run build:packages
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=arm64
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=x64
# Build required out/mobile-web after installing its React Native/Expo dependencies.
- uses: ./.github/actions/install-mobile-dependencies
if: needs.release-javascript.outputs.supported != 'true'
@@ -432,7 +423,7 @@ jobs:
timeout_minutes: 45
max_attempts: 2
retry_wait_seconds: 30
command: if [ ! -f config/scripts/build-darwin-terminal-node.mjs ]; then node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/pinned-node-downloads.mjs darwin-arm64 darwin-x64; fi && node config/scripts/ensure-native-runtime.mjs --runtime=electron && ORCA_MAC_ADHOC=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac dmg zip --${{ matrix.arch }} --publish never
command: node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/pinned-node-downloads.mjs darwin-arm64 darwin-x64 && node config/scripts/ensure-native-runtime.mjs --runtime=electron && ORCA_MAC_ADHOC=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac dmg zip --${{ matrix.arch }} --publish never
env:
ORCA_ADHOC_BUILD_VERSION: ${{ needs.orcad-template-support.outputs.version }}
ORCA_BUILD_COMMIT: ${{ needs.orcad-template-support.outputs.commit }}
@@ -665,12 +656,12 @@ jobs:
fast_release: true
javascript_source_sha: ${{ needs.release-javascript.outputs.supported == 'true' && needs.release-javascript.outputs.source_sha || '' }}
# Wait outside the publisher runner so cold runtime builds cannot exhaust its timeout.
# Prefetch the installer during notarization; the publisher still waits for Mac success.
publish-adhoc-win:
needs: [orcad-template-support, build-adhoc-win, build-adhoc-mac]
needs: [orcad-template-support, build-adhoc-win]
if: >-
!cancelled() && needs.orcad-template-support.result == 'success' &&
needs.build-adhoc-win.result == 'success' && needs.build-adhoc-mac.result == 'success'
needs.build-adhoc-win.result == 'success'
permissions:
contents: read
actions: read
@@ -679,3 +670,4 @@ jobs:
with:
channel: adhoc
tag: v${{ needs.orcad-template-support.outputs.version }}
wait_for_mac_job: build-adhoc-mac
+6 -25
View File
@@ -69,8 +69,8 @@ jobs:
head_sha: ${{ steps.freshness.outputs.head_sha }}
published: ${{ steps.publish_live.outcome == 'success' && 'true' || 'false' }}
runs-on: blacksmith-6vcpu-macos-15
# Allow 360min: 180 for a cold patched-runtime build + 150 for install/publish retries and setup.
timeout-minutes: 360
# Allow 150min for install 3×10 + publish 2×45 + ~25 setup/build/verify, so outer timeout cannot strand cleanup mid-retry.
timeout-minutes: 150
env:
NODE_OPTIONS: --max-old-space-size=4096
steps:
@@ -143,7 +143,7 @@ jobs:
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Cache the patched host Node by exact pin and patch, without restore keys.
# Cache bundled host Node by exact pin without restore keys.
- name: Locate the pinned Node runtime cache
if: steps.freshness.outputs.should_build == 'true'
shell: bash
@@ -154,7 +154,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/Library/Caches/orca-node-runtime
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts', 'config/node-runtime-patches/**', 'config/scripts/build-darwin-terminal-node.mjs', 'config/scripts/pinned-node-downloads.mjs', 'config/scripts/update-node-runtime-pin.mjs', 'config/scripts/macos-terminal-host-bundle.cjs') }}
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts') }}
- name: Cache electron-builder downloads
if: steps.freshness.outputs.should_build == 'true'
@@ -177,14 +177,6 @@ jobs:
retry_wait_seconds: 30
command: pnpm install --frozen-lockfile --cpu=current,x64,arm64
- name: Build and verify the patched terminal runtimes
if: steps.freshness.outputs.should_build == 'true'
timeout-minutes: 180
run: |
pnpm run build:packages
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=arm64
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=x64
# Build required out/mobile-web using mobile’s React Native/Expo install.
- uses: ./.github/actions/install-mobile-dependencies
if: steps.freshness.outputs.should_build == 'true'
@@ -201,23 +193,12 @@ jobs:
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Why: App tokens last one hour, and the cold runtime build alone can outlast that.
- name: Re-mint daily repo token for versioning
id: app_token_version
if: steps.freshness.outputs.should_build == 'true'
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.HOURLY_RELEASE_APP_ID }}
private-key: ${{ secrets.HOURLY_RELEASE_APP_PRIVATE_KEY }}
owner: stablyai
repositories: orca-daily
- name: Compute daily version
id: daily
if: steps.freshness.outputs.should_build == 'true'
shell: bash
env:
GH_TOKEN: ${{ steps.app_token_version.outputs.token }}
GH_TOKEN: ${{ steps.app_token.outputs.token }}
MAIN_REPO_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -322,7 +303,7 @@ jobs:
timeout_minutes: 45
max_attempts: 2
retry_wait_seconds: 30
command: node config/scripts/ensure-native-runtime.mjs --runtime=electron && if [ -f config/scripts/build-mac-notarization-overlap.mjs ]; then ORCA_MAC_DAILY=1 node config/scripts/build-mac-notarization-overlap.mjs --publish always; else ORCA_MAC_DAILY=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish always; fi
command: node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/pinned-node-downloads.mjs darwin-arm64 darwin-x64 && node config/scripts/ensure-native-runtime.mjs --runtime=electron && if [ -f config/scripts/build-mac-notarization-overlap.mjs ]; then ORCA_MAC_DAILY=1 node config/scripts/build-mac-notarization-overlap.mjs --publish always; else ORCA_MAC_DAILY=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish always; fi
env:
# Give the publisher App write access to its configured orca-daily target.
GH_TOKEN: ${{ steps.app_token_publish.outputs.token }}
+14 -22
View File
@@ -105,8 +105,8 @@ jobs:
head_sha: ${{ needs.preflight.outputs.head_sha }}
published: ${{ steps.publish_live.outcome == 'success' && 'true' || 'false' }}
runs-on: blacksmith-6vcpu-macos-15
# Allow 360min: 180 for a cold patched-runtime build + 150 for install/publish retries and setup.
timeout-minutes: 360
# Allow 150min for install 3×10 + publish 2×45 + ~25 setup/build/verify; outer timeout otherwise interrupts cleanup.
timeout-minutes: 150
env:
NODE_OPTIONS: --max-old-space-size=4096
steps:
@@ -119,6 +119,15 @@ jobs:
# Read this repo without persisted credentials; write orca-hourly via env App token (zizmor: artipacked).
persist-credentials: false
- name: Mint hourly repo token
id: app_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.HOURLY_RELEASE_APP_ID }}
private-key: ${{ secrets.HOURLY_RELEASE_APP_PRIVATE_KEY }}
owner: stablyai
repositories: orca-hourly
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm
@@ -132,7 +141,7 @@ jobs:
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Cache the patched host Node by exact pin and patch, without restore keys.
# Cache bundled host Node by exact pin without restore keys.
- name: Locate the pinned Node runtime cache
shell: bash
run: echo "ORCA_NODE_RUNTIME_CACHE_DIR=$HOME/Library/Caches/orca-node-runtime" >>"$GITHUB_ENV"
@@ -141,7 +150,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/Library/Caches/orca-node-runtime
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts', 'config/node-runtime-patches/**', 'config/scripts/build-darwin-terminal-node.mjs', 'config/scripts/pinned-node-downloads.mjs', 'config/scripts/update-node-runtime-pin.mjs', 'config/scripts/macos-terminal-host-bundle.cjs') }}
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts') }}
- name: Cache electron-builder downloads
uses: actions/cache@v5
@@ -162,13 +171,6 @@ jobs:
retry_wait_seconds: 30
command: pnpm install --frozen-lockfile --cpu=current,x64,arm64
- name: Build and verify the patched terminal runtimes
timeout-minutes: 180
run: |
pnpm run build:packages
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=arm64
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=x64
# Build required out/mobile-web from mobile’s React Native/Expo install.
- uses: ./.github/actions/install-mobile-dependencies
@@ -182,16 +184,6 @@ jobs:
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Why here: App tokens last one hour, and the cold runtime build alone can outlast that.
- name: Mint hourly repo token
id: app_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.HOURLY_RELEASE_APP_ID }}
private-key: ${{ secrets.HOURLY_RELEASE_APP_PRIVATE_KEY }}
owner: stablyai
repositories: orca-hourly
- name: Compute hourly version
id: hourly
shell: bash
@@ -290,7 +282,7 @@ jobs:
timeout_minutes: 45
max_attempts: 2
retry_wait_seconds: 30
command: node config/scripts/ensure-native-runtime.mjs --runtime=electron && if [ -f config/scripts/build-mac-notarization-overlap.mjs ]; then ORCA_MAC_HOURLY=1 node config/scripts/build-mac-notarization-overlap.mjs --publish always; else ORCA_MAC_HOURLY=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish always; fi
command: node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/pinned-node-downloads.mjs darwin-arm64 darwin-x64 && node config/scripts/ensure-native-runtime.mjs --runtime=electron && if [ -f config/scripts/build-mac-notarization-overlap.mjs ]; then ORCA_MAC_HOURLY=1 node config/scripts/build-mac-notarization-overlap.mjs --publish always; else ORCA_MAC_HOURLY=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish always; fi
env:
# Give publisher token write access to configured orca-hourly repo.
GH_TOKEN: ${{ steps.app_token_publish.outputs.token }}
-34
View File
@@ -50,7 +50,6 @@ jobs:
windows_process_probe: ${{ steps.filter.outputs.windows_process_probe }}
package_windows: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.package_windows }}
layout_oracle: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.layout_oracle }}
darwin_terminal_node: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.darwin_terminal_node }}
dependency_isolation: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.dependency_isolation }}
e2e_should_run: ${{ steps.e2e_filter.outputs.should_run }}
test_files: ${{ steps.e2e_filter.outputs.test_files }}
@@ -77,7 +76,6 @@ jobs:
/config/.bun-version
/config/scripts/check-readme-local-links.mjs
/config/scripts/pr-code-change-scope.mjs
/config/scripts/pr-darwin-terminal-node-route.mjs
/.oxfmtrc.json
/config/scripts/pr-package-test-paths.mjs
/config/scripts/pr-layout-oracle-route.mjs
@@ -1496,34 +1494,6 @@ jobs:
*) echo "node_modules/zod is not in the isolated store"; exit 1 ;;
esac
darwin_terminal_node:
name: mac terminal worker wakeup (${{ matrix.os }})
needs: [code_paths, preflight]
if: >-
!cancelled() && needs.preflight.result == 'success' &&
needs.code_paths.outputs.darwin_terminal_node == 'true'
strategy:
fail-fast: false
matrix:
os: [macos-15, macos-15-intel]
runs-on: ${{ matrix.os }}
timeout-minutes: 180
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
- uses: actions/cache@v5
with:
path: out/node-runtime-cache
key: darwin-terminal-node-${{ runner.arch }}-${{ hashFiles('src/shared/node-runtime-pin.ts', 'config/node-runtime-patches/**', 'config/scripts/build-darwin-terminal-node.mjs', 'config/scripts/pinned-node-downloads.mjs', 'config/scripts/update-node-runtime-pin.mjs', 'config/scripts/macos-terminal-host-bundle.cjs') }}
- name: Build and verify the shipping terminal runtime
run: node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs
verify:
name: PR checks passed
# Publish an explicit result after every required dependency settles.
@@ -1541,7 +1511,6 @@ jobs:
- managed_hook_node18
- package
- package_windows
- darwin_terminal_node
- layout_oracle
- dependency_isolation
# Report settled results without competing with the GitHub-hosted test queue.
@@ -1578,8 +1547,6 @@ jobs:
PACKAGE_SHOULD_RUN: ${{ needs.code_paths.outputs.package }}
PACKAGE_WINDOWS: ${{ needs.package_windows.result }}
PACKAGE_WINDOWS_SHOULD_RUN: ${{ needs.code_paths.outputs.package_windows }}
DARWIN_TERMINAL_NODE: ${{ needs.darwin_terminal_node.result }}
DARWIN_TERMINAL_NODE_SHOULD_RUN: ${{ needs.code_paths.outputs.darwin_terminal_node }}
LAYOUT_ORACLE: ${{ needs.layout_oracle.result }}
LAYOUT_ORACLE_SHOULD_RUN: ${{ needs.code_paths.outputs.layout_oracle }}
DEPENDENCY_ISOLATION: ${{ needs.dependency_isolation.result }}
@@ -1625,7 +1592,6 @@ jobs:
check_job managed_hook_node18 "$MANAGED_HOOK_NODE18" "$MANAGED_HOOK_NODE18_SHOULD_RUN"
check_job package "$PACKAGE" "$PACKAGE_SHOULD_RUN"
check_job package_windows "$PACKAGE_WINDOWS" "$PACKAGE_WINDOWS_SHOULD_RUN"
check_job darwin_terminal_node "$DARWIN_TERMINAL_NODE" "$DARWIN_TERMINAL_NODE_SHOULD_RUN"
check_job layout_oracle "$LAYOUT_ORACLE" "$LAYOUT_ORACLE_SHOULD_RUN"
check_job dependency_isolation "$DEPENDENCY_ISOLATION" "$DEPENDENCY_ISOLATION_SHOULD_RUN"
exit "$failed"
+4 -7
View File
@@ -1071,7 +1071,6 @@ jobs:
- release-preflight
- release-javascript
- build-mac
- verify-mac-build
# Why not the implicit success(): a tag without the orcad template skips that job on purpose.
if: >-
!cancelled() &&
@@ -1081,7 +1080,6 @@ jobs:
needs.release-preflight.result == 'success' &&
needs.release-javascript.result == 'success' &&
needs.build-mac.result == 'success' &&
needs.verify-mac-build.result == 'success' &&
(needs.orcad-template.result == 'success' ||
(needs.orcad-template.result == 'skipped' && needs.cut.outputs.ships_orcad_template == 'false'))
env:
@@ -1469,7 +1467,7 @@ jobs:
path: signing-stage/**
if-no-files-found: error
# Recheck the exact successful macOS run before consuming signing quota.
# Unsigned packaging overlaps macOS; only successful macOS builds may consume signing quota.
- name: Require macOS success before Windows signing
id: mac-signing-gate
if: matrix.platform == 'win' && github.run_attempt == 1
@@ -2080,7 +2078,6 @@ jobs:
(needs.orcad-template.result == 'skipped' && needs.cut.outputs.ships_orcad_template == 'false'))
# Keep every SignPath workflow job GitHub-hosted; separate release-mac-build allows Blacksmith outside Windows provenance.
runs-on: ubuntu-latest
timeout-minutes: 185
permissions:
actions: write
contents: read
@@ -2109,20 +2106,20 @@ jobs:
RELEASE_MAC_BUILD_JAVASCRIPT_SOURCE_SHA: ${{ needs.release-javascript.outputs.supported == 'true' && needs.release-javascript.outputs.source_sha || '' }}
RELEASE_MAC_BUILD_TAG: ${{ needs.cut.outputs.tag }}
RELEASE_MAC_BUILD_WORKFLOW: release-mac-build.yml
RELEASE_MAC_BUILD_OBSERVE_MINUTES: '180'
verify-mac-build:
needs:
- cut
- build-mac
# Finish the shared deadline before starting the Windows signing job's six-hour budget.
- build
# Qualify macOS even when a sibling build fails; publication still requires every build.
if: >-
!cancelled() &&
needs.cut.result == 'success' &&
needs.cut.outputs.should_release == 'true' &&
needs.build-mac.result == 'success'
runs-on: ubuntu-latest
timeout-minutes: 215
timeout-minutes: 95
permissions:
actions: read
contents: read
+4 -14
View File
@@ -49,8 +49,7 @@ jobs:
if: ${{ !cancelled() && (!inputs.qualification_only || needs.qualification-template.result == 'success') && github.repository == 'stablyai/orca' }}
# Keep Blacksmith mac notarization outside SignPath’s GitHub-hosted Windows provenance.
runs-on: blacksmith-6vcpu-macos-15
# Includes the 180-minute cold runtime build and the existing packaging retry budget.
timeout-minutes: 360
timeout-minutes: 60
# actions: read fetches the release-cut run's artifacts.
permissions:
actions: read
@@ -96,7 +95,7 @@ jobs:
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Cache the patched host Node by exact pin and patch, without restore keys.
# Cache bundled host Node by exact pin, without restore keys.
- name: Locate the pinned Node runtime cache
shell: bash
run: echo "ORCA_NODE_RUNTIME_CACHE_DIR=$HOME/Library/Caches/orca-node-runtime" >>"$GITHUB_ENV"
@@ -105,7 +104,7 @@ jobs:
uses: actions/cache@v5
with:
path: ~/Library/Caches/orca-node-runtime
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts', 'config/node-runtime-patches/**', 'config/scripts/build-darwin-terminal-node.mjs', 'config/scripts/pinned-node-downloads.mjs', 'config/scripts/update-node-runtime-pin.mjs', 'config/scripts/macos-terminal-host-bundle.cjs') }}
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts') }}
# Cache Electron/builder tool downloads to save ~30–90s.
- name: Cache electron-builder downloads
@@ -127,15 +126,6 @@ jobs:
retry_wait_seconds: 30
command: pnpm install --frozen-lockfile --cpu=current,x64,arm64
# Why: older refs predate the source build and package the pinned download instead.
- name: Build and verify the patched terminal runtimes
if: hashFiles('config/scripts/build-darwin-terminal-node.mjs') != ''
timeout-minutes: 180
run: |
pnpm run build:packages
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=arm64
ORCA_BACKGROUND_LAUNCH=1 node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/verify-darwin-terminal-node.mjs --arch=x64
# Build required out/mobile-web using mobile’s React Native/Expo install.
- uses: ./.github/actions/install-mobile-dependencies
@@ -235,7 +225,7 @@ jobs:
timeout_minutes: 45
max_attempts: 3
retry_wait_seconds: 30
command: if [ ! -f config/scripts/build-darwin-terminal-node.mjs ]; then node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/pinned-node-downloads.mjs darwin-arm64 darwin-x64; fi && node config/scripts/ensure-native-runtime.mjs --runtime=electron && if [ -f config/scripts/build-mac-notarization-overlap.mjs ]; then ORCA_MAC_RELEASE=1 node config/scripts/build-mac-notarization-overlap.mjs --publish "$ORCA_MAC_PUBLISH" --draft; else ORCA_MAC_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish "$ORCA_MAC_PUBLISH" -c.publish.releaseType=draft; fi
command: node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON config/scripts/pinned-node-downloads.mjs darwin-arm64 darwin-x64 && node config/scripts/ensure-native-runtime.mjs --runtime=electron && if [ -f config/scripts/build-mac-notarization-overlap.mjs ]; then ORCA_MAC_RELEASE=1 node config/scripts/build-mac-notarization-overlap.mjs --publish "$ORCA_MAC_PUBLISH" --draft; else ORCA_MAC_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish "$ORCA_MAC_PUBLISH" -c.publish.releaseType=draft; fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORCA_REQUIRE_ORCAD_TEMPLATE: '1'
@@ -1,7 +0,0 @@
{
"version": "24.22.1",
"sourceArchive": "node-v24.22.1.tar.gz",
"sourceSha256": "001773d9bd53fbd27a25efd42aa6a9693ebfe500998ca9bec635e9b6ee79bc91",
"recipeVersion": 1,
"workerSourceSha256": "06ab0be8159c22cfb5f4a715d776693825835f1369c7c82d13a22e607947b4fd"
}
@@ -1,46 +0,0 @@
--- a/deps/uv/src/threadpool.c
+++ b/deps/uv/src/threadpool.c
@@ -26,6 +26,9 @@
#endif
#include <stdlib.h>
+#if defined(__APPLE__) && defined(__MACH__)
+# include <errno.h>
+#endif
#define MAX_THREADPOOL_SIZE 1024
@@ -51,6 +54,24 @@
}
+static void wait_for_work(void) {
+#if defined(__APPLE__) && defined(__MACH__)
+ /* Bound idle waits so Darwin psynch lost wakeups can recover. */
+ const struct timespec timeout = { 30, 0 };
+ int r;
+
+ errno = 0;
+ r = pthread_cond_timedwait_relative_np(&cond, &mutex, &timeout);
+ /* Preserve uv_cond_wait's EINVAL/EBUSY workaround for Darwin. */
+ if (r == 0 || r == ETIMEDOUT || (r == EINVAL && errno == EBUSY))
+ return;
+ abort();
+#else
+ uv_cond_wait(&cond, &mutex);
+#endif
+}
+
+
/* To avoid deadlock with uv_cancel() it's crucial that the worker
* never holds the global mutex and the loop-local mutex at the same time.
*/
@@ -74,7 +95,7 @@
uv__queue_next(&run_slow_work_message) == &wq &&
slow_io_work_running >= slow_work_thread_threshold())) {
idle_threads += 1;
- uv_cond_wait(&cond, &mutex);
+ wait_for_work();
idle_threads -= 1;
}
-73
View File
@@ -484,79 +484,6 @@
],
"demotionRule": "Keep experimental or demote if recovery signals an unverified owner, treats loss of contact as exit, reports success before descendant exit, adds background scanning, or lets a recovery failure block the current-daemon restart."
},
{
"id": "terminal-runtime.darwin-worker-wakeup",
"title": "Idle Darwin workers refresh their waits and continue delivering asynchronous terminal work",
"maturity": "experimental",
"protection": "partial",
"owner": "terminal-runtime",
"layer": "native-runtime",
"surfaces": ["packaged macOS terminal helper", "terminal input", "terminal creation"],
"platforms": ["macos"],
"providers": ["daemon", "local", "ssh", "remote-runtime", "mobile-relay"],
"coveredPlatforms": ["macos"],
"coveredProviders": ["daemon"],
"coverageNotes": "The repository arm64 build and x64 build under Rosetta passed real PTY input, filesystem/DNS/crypto and idle-wait generation checks; physical ARM and Intel CI passed on Node 24.21.0. Node 24.22.1 arm64 passed the same native oracle locally; its official unpatched runtime failed idle refresh. Fresh physical ARM/Intel CI remains required. An isolated signed installed helper with patched Node also served a PTY. A complete package build and older macOS remain unverified. Standalone/SSH servers and the Electron fallback do not receive the patch; mobile/relay inherit only their execution host runtime.",
"motivatingLinks": [
"docs/bug-reproductions/darwin-worker-wakeup.md",
"https://github.com/libuv/libuv/pull/5351"
],
"invariant": "A packaged Darwin terminal worker must not remain indefinitely parked on a stale condition generation while asynchronous work is queued; process and session ownership must remain unchanged.",
"oracle": "Four real idle workers each advance their low 32-bit psynch wait generation within the 31-second observation interval, with at most sixteen generation steps, and real PTY input plus filesystem/DNS/crypto work completes. The upstream half-range witness stalls unpatched workers with queued jobs and completes with the patch.",
"commands": ["ORCA_BACKGROUND_LAUNCH=1 node config/scripts/verify-darwin-terminal-node.mjs"],
"testFiles": ["config/scripts/verify-darwin-terminal-node.mjs"],
"assertionRefs": [
{
"file": "config/scripts/verify-darwin-terminal-node.mjs",
"assertions": ["Runtime proof failed"]
}
],
"evidenceRuns": [
{
"date": "2026-10-23",
"runner": "local",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 node config/scripts/verify-darwin-terminal-node.mjs",
"result": "passed",
"durationSeconds": 2231,
"summary": "Cold source build of pinned Node 24.22.1 arm64 passed architecture/minimum-OS checks, real PTY input, fs/DNS/crypto and all four bounded idle-wait generation checks. Official Node 24.22.1 failed the same idle-refresh assertion after passing the initial PTY and I/O checks. Timing includes cold compilation and source cleanup."
},
{
"date": "2026-10-15",
"runner": "manual",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 node config/scripts/verify-darwin-terminal-node.mjs",
"result": "passed",
"durationSeconds": 32,
"summary": "Repository-built Node 24.21.0 arm64 passed real PTY input, fs/DNS/crypto and all four bounded idle-generation checks. Cached verifier execution took about 32 seconds; this is not full package-build proof."
}
],
"runtimeBudget": {
"p95Seconds": 10800,
"scope": "Cold pinned Node source compilation dominates; Intel exceeded the initial 90-minute job allowance, now 180 minutes. Cached native runtime probe takes about 32 seconds. No CI soak yet."
},
"performanceBudget": {
"required": true,
"evidence": "One relative timeout per idle worker per 30 seconds, at most eight wakes/minute for the default pool; immediate signals remain. Generation advancement is bounded by the native probe. No application polling or session scans added."
},
"flakeHistory": {
"status": "not-started",
"evidence": "Focused native and manual Node checks passed on arm64; both physical architectures passed on Node 24.21.0; the updated recipe and repeated CI soak remain pending."
},
"redGreenEvidence": {
"status": "complete",
"evidence": "Upstream real-worker half-range witness: baseline four idle workers with zero jobs/callbacks and queued work; patch four jobs/callbacks and empty queue. An ordinary sequential workload also reproduced the exact half-range signature, zero fresh stat callbacks and no real PTY echo after 15 seconds. The patched runtime completed 30 million sequential jobs, a fresh stat and real PTY echo. Idle-generation regression fails without the patch and passes with it."
},
"promotionCriteria": [
"Collect physical arm64/Intel CI, signed helper launch and older macOS evidence, then repeated soak before promotion."
],
"knownGaps": [
"Ordinary sequential jobs reproduced half-range waiter starvation and PTY input loss; scheduling is intermittent and the exact Orca job-producer sequence remains unknown.",
"No older macOS, full package build, launchd/TCC attribution or battery measurement yet.",
"Existing daemons require deliberate restart; standalone/SSH macOS servers and Electron fallback remain unpatched."
],
"demotionRule": "Keep experimental until native runtime and signed packaging evidence is stable; investigate failures without weakening generation bounds or silently using an official unpatched binary."
},
{
"id": "agent-session.delayed-terminal-launch",
"title": "Delayed terminal launches retain their harness preparation and recover process discovery",
@@ -1,128 +0,0 @@
#include <node_api.h>
#include <stdio.h>
#include <stdlib.h>
#include "darwin-threadpool-wait.h"
static uv_work_t requests[4];
static uv_thread_t workers[4];
static uv_sem_t arrived;
static uv_sem_t release_workers;
static unsigned callbacks;
static int started;
static uv_work_t stress_request;
static uv_loop_t* stress_loop;
static unsigned stress_completed;
static const unsigned stress_target = 30000000;
static void stress_work(uv_work_t* request) {
(void) request;
}
static void stress_done(uv_work_t* request, int status) {
if (status != 0)
abort();
if (++stress_completed < stress_target &&
uv_queue_work(stress_loop, request, stress_work, stress_done) != 0)
abort();
}
static napi_value start_stress(napi_env env, napi_callback_info info) {
napi_value result;
(void) info;
if (stress_loop != NULL ||
napi_get_uv_event_loop(env, &stress_loop) != napi_ok ||
uv_queue_work(stress_loop, &stress_request, stress_work, stress_done) != 0)
abort();
napi_get_undefined(env, &result);
return result;
}
static napi_value stress_progress(napi_env env, napi_callback_info info) {
napi_value result;
(void) info;
napi_create_uint32(env, stress_completed, &result);
return result;
}
static void enroll_worker(uv_work_t* request) {
workers[request - requests] = uv_thread_self();
uv_sem_post(&arrived);
uv_sem_wait(&release_workers);
}
static void complete_work(uv_work_t* request, int status) {
(void) request;
if (status != 0)
abort();
if (++callbacks == 4) {
uv_sem_destroy(&arrived);
uv_sem_destroy(&release_workers);
}
}
static napi_value enroll(napi_env env, napi_callback_info info) {
uv_loop_t* loop;
napi_value result;
unsigned i;
(void) info;
if (started) {
napi_throw_error(env, NULL, "Workers already enrolled");
return NULL;
}
started = 1;
if (napi_get_uv_event_loop(env, &loop) != napi_ok ||
uv_sem_init(&arrived, 0) != 0 ||
uv_sem_init(&release_workers, 0) != 0)
abort();
for (i = 0; i < 4; i++)
if (uv_queue_work(loop, requests + i, enroll_worker, complete_work) != 0)
abort();
for (i = 0; i < 4; i++)
uv_sem_wait(&arrived);
for (i = 0; i < 4; i++)
uv_sem_post(&release_workers);
napi_get_undefined(env, &result);
return result;
}
static napi_value snapshot(napi_env env, napi_callback_info info) {
char output[2048];
uintptr_t condition;
uint64_t generation;
napi_value result;
unsigned i;
int at;
int parked;
(void) info;
if (!started) {
napi_throw_error(env, NULL, "Enroll workers first");
return NULL;
}
at = snprintf(output, sizeof(output),
"{\"callbacks\":%u,\"waits\":[", callbacks);
for (i = 0; i < 4; i++) {
generation = 0;
parked = read_wait_generation(workers[i], &condition, &generation);
at += snprintf(output + at, sizeof(output) - at,
"%s{\"parked\":%d,\"generation\":\"%llx\"}",
i ? "," : "", parked, (unsigned long long) generation);
}
snprintf(output + at, sizeof(output) - at, "]}");
napi_create_string_utf8(env, output, NAPI_AUTO_LENGTH, &result);
return result;
}
NAPI_MODULE_INIT() {
napi_value function;
napi_create_function(env, "enroll", NAPI_AUTO_LENGTH, enroll, NULL, &function);
napi_set_named_property(env, exports, "enroll", function);
napi_create_function(env, "snapshot", NAPI_AUTO_LENGTH, snapshot, NULL, &function);
napi_set_named_property(env, exports, "snapshot", function);
napi_create_function(env, "startStress", NAPI_AUTO_LENGTH,
start_stress, NULL, &function);
napi_set_named_property(env, exports, "startStress", function);
napi_create_function(env, "stressProgress", NAPI_AUTO_LENGTH,
stress_progress, NULL, &function);
napi_set_named_property(env, exports, "stressProgress", function);
return exports;
}
@@ -1,68 +0,0 @@
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef THREADPOOL_DARWIN_WAIT_H_
#define THREADPOOL_DARWIN_WAIT_H_
#include "uv.h"
#include <dlfcn.h>
#include <mach/mach.h>
#include <mach/thread_status.h>
#include <pthread.h>
#include <stdint.h>
#include <string.h>
static int read_wait_generation(uv_thread_t worker,
uintptr_t* condition,
uint64_t* generation) {
mach_msg_type_number_t count;
kern_return_t result;
uintptr_t pc;
Dl_info symbol;
# if defined(__aarch64__)
arm_thread_state64_t state = {0};
count = ARM_THREAD_STATE64_COUNT;
result = thread_get_state(pthread_mach_thread_np(worker),
ARM_THREAD_STATE64,
(thread_state_t) &state,
&count);
pc = arm_thread_state64_get_pc(state);
*generation = state.__x[1];
*condition = state.__x[0];
# else
x86_thread_state64_t state = {0};
count = x86_THREAD_STATE64_COUNT;
result = thread_get_state(pthread_mach_thread_np(worker),
x86_THREAD_STATE64,
(thread_state_t) &state,
&count);
pc = state.__rip;
*generation = state.__rsi;
*condition = state.__rdi;
# endif
if (result != KERN_SUCCESS)
return 0;
if (!dladdr((void*) pc, &symbol) || symbol.dli_sname == NULL)
return 0;
return strcmp(symbol.dli_sname, "__psynch_cvwait") == 0;
}
#endif
@@ -117,20 +117,15 @@ describe('adhoc Windows compilation overlaps Mac without publishing early', () =
})
it.each(['success', 'failure', 'cancelled', 'skipped', ''])(
'publishes only after both Windows compilation and macOS publication succeed: %s',
'prefetches only after Windows compilation succeeds: %s',
(result) => {
const run = compileAggregateExpression(publish.if)
const needs = {
'orcad-template-support': { result: 'success' },
'build-adhoc-win': { result: 'success' },
'build-adhoc-mac': { result: 'success' }
'build-adhoc-win': { result: 'success' }
}
expect(publish.needs).toEqual([
'orcad-template-support',
'build-adhoc-win',
'build-adhoc-mac'
])
expect(publish.with.wait_for_mac_job).toBeUndefined()
expect(publish.needs).toEqual(['orcad-template-support', 'build-adhoc-win'])
expect(publish.with.wait_for_mac_job).toBe('build-adhoc-mac')
expect(run({ needs }).coerceString()).toBe('true')
expect(run({ needs }, true).coerceString()).toBe('false')
for (const job of publish.needs) {
@@ -1,222 +0,0 @@
import { createHash } from 'node:crypto'
import {
chmodSync,
copyFileSync,
mkdirSync,
mkdtempSync,
readFileSync,
renameSync,
rmSync,
writeFileSync
} from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { lock } from 'proper-lockfile'
import { runProcess } from '@orca/process-host'
import {
NODE_RUNTIME_DARWIN_MINIMUM_OS,
NODE_RUNTIME_PIN
} from '../../src/shared/node-runtime-pin.ts'
import { fetchPinnedFile, pinnedNodeCacheDir } from './pinned-node-downloads.mjs'
import { sha256File } from './update-node-runtime-pin.mjs'
import { getTarProgram } from './zip-extractor-command.mjs'
import { isDirectInvocation } from './script-entry-detection.mjs'
const root = resolve(import.meta.dirname, '../..')
const patchPath = join(root, 'config/node-runtime-patches/darwin-worker-wait.patch')
const recipe = JSON.parse(
readFileSync(join(root, 'config/node-runtime-patches/darwin-worker-wait.json'), 'utf8')
)
const require = createRequire(import.meta.url)
const { lipoArchsToNodeArchs, parseBuildVersionMinos } = require('./macos-terminal-host-bundle.cjs')
const builds = new Map()
export function darwinTerminalNodeInputs(arch, version = NODE_RUNTIME_PIN.version) {
if (arch !== 'arm64' && arch !== 'x64') {
throw new Error(`Unsupported macOS terminal Node architecture: ${arch}`)
}
if (version !== recipe.version) {
throw new Error(`Review the Darwin worker-wait patch before moving Node to ${version}`)
}
return {
...recipe,
arch,
...(arch !== process.arch
? {
crossBuildMode: arch === 'x64' ? 'target-tools-under-rosetta' : 'separate-host-tools'
}
: {}),
patchSha256: createHash('sha256').update(readFileSync(patchPath)).digest('hex'),
buildScriptsSha256: Object.fromEntries(
[
'build-darwin-terminal-node.mjs',
'pinned-node-downloads.mjs',
'update-node-runtime-pin.mjs',
'macos-terminal-host-bundle.cjs'
].map((file) => [
file,
createHash('sha256')
.update(readFileSync(join(import.meta.dirname, file)))
.digest('hex')
])
),
minimumOs: NODE_RUNTIME_DARWIN_MINIMUM_OS
}
}
export async function readBuiltDarwinTerminalNode(directory, inputs) {
try {
const receipt = JSON.parse(readFileSync(join(directory, 'receipt.json'), 'utf8'))
if (JSON.stringify(receipt.inputs) !== JSON.stringify(inputs)) {
return null
}
const executable = join(directory, 'node')
return (await sha256File(executable)) === receipt.executableSha256 ? executable : null
} catch {
return null
}
}
async function runBuildCommand(program, args, options = {}) {
const result = await runProcess(
{
program,
args,
timeoutMs: 180 * 60_000,
maxOutputBytes: 1024 * 1024,
...options
},
'tail'
)
if (result.code !== 0) {
throw new Error(`${program} failed (${result.code}):\n${result.stderr}\n${result.stdout}`)
}
return result.stdout
}
async function compileDarwinTerminalNode(directory, inputs, cacheDir, scratch) {
const archivePath = join(cacheDir, inputs.sourceArchive)
const releaseArchive = await lock(archivePath, {
realpath: false,
stale: 60_000,
retries: { retries: 2160, minTimeout: 5_000, maxTimeout: 5_000 }
})
let archive
try {
archive = await fetchPinnedFile({
url: `https://nodejs.org/dist/v${inputs.version}/${inputs.sourceArchive}`,
destination: archivePath,
sha256: inputs.sourceSha256
})
} finally {
await releaseArchive()
}
const source = join(scratch, `node-v${inputs.version}`)
await runBuildCommand(getTarProgram(), [
'-xzf',
archive,
'-C',
scratch,
`node-v${inputs.version}`
])
await runBuildCommand('patch', ['--batch', '--forward', '--fuzz=0', '-p1', '-i', patchPath], {
cwd: source
})
if ((await sha256File(join(source, 'deps/uv/src/threadpool.c'))) !== inputs.workerSourceSha256) {
throw new Error('The Node worker source does not match the reviewed patch')
}
const env = {
...process.env,
ORCA_BACKGROUND_LAUNCH: '1',
MACOSX_DEPLOYMENT_TARGET: inputs.minimumOs
}
const args = ['--without-npm', '--without-corepack', `--dest-cpu=${inputs.arch}`]
if (process.arch !== inputs.arch) {
const compilerArch = inputs.arch === 'x64' ? 'x86_64' : 'arm64'
env.CC = `clang -arch ${compilerArch}`
env.CXX = `clang++ -arch ${compilerArch}`
if (inputs.arch === 'x64') {
await runBuildCommand('/usr/bin/arch', ['-x86_64', '/usr/bin/true'])
env.CC_host = env.CC
env.CXX_host = env.CXX
args.push('--no-cross-compiling')
} else {
env.CC_host = 'clang -arch x86_64'
env.CXX_host = 'clang++ -arch x86_64'
args.push('--cross-compiling')
}
}
await runBuildCommand(join(source, 'configure'), args, { cwd: source, env })
console.log(`[terminal-node] Building patched Node ${inputs.version} (${inputs.arch})`)
await runBuildCommand('make', ['-j4', 'node'], { cwd: source, env })
const executable = join(source, 'out/Release/node')
const architectures = lipoArchsToNodeArchs(await runBuildCommand('lipo', ['-archs', executable]))
const minimumOs = parseBuildVersionMinos(await runBuildCommand('otool', ['-l', executable]))
if (
architectures.length !== 1 ||
architectures[0] !== inputs.arch ||
minimumOs !== inputs.minimumOs
) {
throw new Error('Patched terminal Node has the wrong architecture or macOS floor')
}
const staged = join(directory, `node.${process.pid}.partial`)
copyFileSync(executable, staged)
chmodSync(staged, 0o755)
const executableSha256 = await sha256File(staged)
renameSync(staged, join(directory, 'node'))
const receipt = join(directory, `receipt.${process.pid}.partial`)
writeFileSync(receipt, `${JSON.stringify({ inputs, executableSha256 }, null, 2)}\n`)
renameSync(receipt, join(directory, 'receipt.json'))
return join(directory, 'node')
}
export function ensureDarwinTerminalNode({ arch, cacheDir = pinnedNodeCacheDir() }) {
if (process.platform !== 'darwin') {
throw new Error('Build the patched macOS terminal runtime on a macOS host')
}
const inputs = darwinTerminalNodeInputs(arch)
const key = createHash('sha256').update(JSON.stringify(inputs)).digest('hex')
const directory = join(cacheDir, 'darwin-terminal-node', key)
const pending = builds.get(directory)
if (pending) {
return pending
}
const build = (async () => {
mkdirSync(directory, { recursive: true })
const cached = await readBuiltDarwinTerminalNode(directory, inputs)
if (cached) {
return cached
}
const release = await lock(directory, {
realpath: false,
stale: 60_000,
retries: { retries: 2160, minTimeout: 5_000, maxTimeout: 5_000 }
})
let scratch
try {
const cached = await readBuiltDarwinTerminalNode(directory, inputs)
if (cached) {
return cached
}
scratch = mkdtempSync(join(tmpdir(), 'orca-darwin-terminal-node-build-'))
return await compileDarwinTerminalNode(directory, inputs, cacheDir, scratch)
} finally {
try {
// Release before synchronous cleanup can starve the lease heartbeat.
await release()
} finally {
if (scratch) {
rmSync(scratch, { recursive: true, force: true })
}
}
}
})()
builds.set(directory, build)
void build.finally(() => builds.delete(directory)).catch(() => {})
return build
}
if (isDirectInvocation(import.meta.url, process.argv[1])) {
console.log(await ensureDarwinTerminalNode({ arch: process.argv[2] ?? process.arch }))
}
@@ -1,92 +0,0 @@
import { createHash } from 'node:crypto'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, expect, it } from 'vitest'
import {
darwinTerminalNodeInputs,
readBuiltDarwinTerminalNode
} from './build-darwin-terminal-node.mjs'
const directories = []
afterEach(() => {
for (const directory of directories.splice(0)) {
rmSync(directory, { recursive: true, force: true })
}
})
function artifact() {
const directory = mkdtempSync(join(tmpdir(), 'darwin-terminal-node-cache-'))
directories.push(directory)
const inputs = darwinTerminalNodeInputs('arm64')
const bytes = Buffer.from('built Node artifact')
const executable = join(directory, 'node')
writeFileSync(executable, bytes)
writeFileSync(
join(directory, 'receipt.json'),
JSON.stringify({
inputs,
executableSha256: createHash('sha256').update(bytes).digest('hex')
})
)
return { directory, inputs, executable }
}
it('requires review when the Node pin moves instead of silently dropping the fix', () => {
expect(() => darwinTerminalNodeInputs('arm64', '99.0.0')).toThrow('Review the Darwin')
expect(() => darwinTerminalNodeInputs('riscv64')).toThrow('Unsupported')
})
it('separates architecture and includes the exact patch and source identity', () => {
const arm = darwinTerminalNodeInputs('arm64')
const x64 = darwinTerminalNodeInputs('x64')
expect(arm.arch).not.toBe(x64.arch)
expect(arm.sourceSha256).toMatch(/^[0-9a-f]{64}$/)
expect(arm.workerSourceSha256).toMatch(/^[0-9a-f]{64}$/)
expect(arm.patchSha256).toBe(
createHash('sha256')
.update(
readFileSync(join(import.meta.dirname, '../node-runtime-patches/darwin-worker-wait.patch'))
)
.digest('hex')
)
})
it('reuses a built artifact only when its bytes and every build input match', async () => {
const { directory, inputs, executable } = artifact()
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBe(executable)
expect(await readBuiltDarwinTerminalNode(directory, { ...inputs, arch: 'x64' })).toBeNull()
expect(
await readBuiltDarwinTerminalNode(directory, { ...inputs, patchSha256: 'other' })
).toBeNull()
})
it('rejects a tampered cached executable', async () => {
const { directory, inputs, executable } = artifact()
writeFileSync(executable, 'different bytes')
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBeNull()
})
it('rejects unchanged executable bytes built with an older build script', async () => {
const { directory, inputs, executable } = artifact()
const receiptPath = join(directory, 'receipt.json')
const receipt = JSON.parse(readFileSync(receiptPath, 'utf8'))
expect(inputs.buildScriptsSha256['build-darwin-terminal-node.mjs']).toMatch(/^[0-9a-f]{64}$/)
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBe(executable)
receipt.inputs.buildScriptsSha256['build-darwin-terminal-node.mjs'] = 'older build recipe'
writeFileSync(receiptPath, JSON.stringify(receipt))
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBeNull()
delete receipt.inputs.buildScriptsSha256
writeFileSync(receiptPath, JSON.stringify(receipt))
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBeNull()
})
it('rejects a partial build, invalid receipt, or non-file executable', async () => {
const { directory, inputs, executable } = artifact()
rmSync(executable)
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBeNull()
mkdirSync(executable)
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBeNull()
writeFileSync(join(directory, 'receipt.json'), '{')
expect(await readBuiltDarwinTerminalNode(directory, inputs)).toBeNull()
})
@@ -1,80 +0,0 @@
const assert = require('node:assert/strict')
const { setTimeout: delay } = require('node:timers/promises')
const fs = require('node:fs/promises')
const crypto = require('node:crypto')
const dns = require('node:dns/promises')
const pty = require(process.argv[3])
const canary = require(process.argv[2])
const deadline = setTimeout(() => process.exit(2), 45_000)
async function checkIo() {
await Promise.all([
fs.stat(process.execPath),
dns.lookup('localhost'),
new Promise((resolve, reject) => {
crypto.randomBytes(32, (error) => (error ? reject(error) : resolve()))
})
])
}
async function checkPty() {
const terminal = pty.spawn('/bin/sh', ['-c', 'read line; printf "ECHO:%s\\n" "$line"'], {
cwd: process.argv[4],
env: process.env
})
let output = ''
terminal.onData((chunk) => {
output += chunk
})
const exited = new Promise((resolve, reject) => {
terminal.onExit(({ exitCode }) =>
exitCode === 0 ? resolve() : reject(new Error('PTY failed'))
)
})
terminal.write('ORCA_WORKER_WAKEUP_TEST\r')
await exited
assert.ok(output.includes('ECHO:ORCA_WORKER_WAKEUP_TEST'))
}
async function main() {
assert.equal(process.version, process.argv[5])
await checkIo()
await checkPty()
canary.enroll()
let before
for (let i = 0; i < 100; i++) {
await delay(10)
before = JSON.parse(canary.snapshot())
if (before.callbacks === 4 && before.waits.every((wait) => wait.parked)) {
break
}
}
assert.equal(before.callbacks, 4)
assert.ok(before.waits.every((wait) => wait.parked))
await delay(31_000)
const after = JSON.parse(canary.snapshot())
assert.ok(after.waits.every((wait) => wait.parked))
for (let i = 0; i < 4; i++) {
const previous = BigInt(`0x${before.waits[i].generation}`)
const current = BigInt(`0x${after.waits[i].generation}`)
const advances = BigInt.asUintN(32, current - previous)
assert.ok(advances > 0n)
assert.ok(advances <= 16n * 256n)
}
await checkIo()
clearTimeout(deadline)
console.log(
JSON.stringify({
node: process.version,
uv: process.versions.uv,
arch: process.arch,
fsDnsCrypto: 'passed',
ptyInput: 'passed',
idleWaitRefresh: 'passed'
})
)
}
main().catch((error) => {
console.error(error)
process.exit(1)
})
@@ -1,147 +0,0 @@
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { runProcess } from '@orca/process-host'
import { expect, it } from 'vitest'
import { parse } from 'yaml'
import { readDarwinVerificationArch } from './verify-darwin-terminal-node.mjs'
import { classifyPrJobs } from './pr-code-change-scope.mjs'
function workflow(name) {
return parse(
readFileSync(join(import.meta.dirname, '../../.github/workflows', `${name}.yml`), 'utf8')
)
}
it.each([
'config/node-runtime-patches/darwin-worker-wait.patch',
'config/scripts/build-darwin-terminal-node.mjs',
'config/scripts/pr-darwin-terminal-node-route.mjs',
'config/scripts/verify-darwin-terminal-node.mjs',
'config/scripts/darwin-terminal-node-probe.cjs',
'config/scripts/__fixtures__/darwin-threadpool-wait.h',
'config/scripts/macos-terminal-host-bundle.cjs',
'config/scripts/pinned-node-downloads.mjs',
'config/scripts/update-node-runtime-pin.mjs',
'config/scripts/orcad-prebuild-node-gyp.mjs',
'config/scripts/orcad-prebuild-slot-contents.mjs',
'src/shared/node-runtime-pin.ts'
])('qualifies the actual macOS runtime when %s changes', (file) => {
expect(classifyPrJobs([file]).darwin_terminal_node).toBe(true)
})
it('does not source-build Node for unrelated renderer changes', () => {
expect(classifyPrJobs(['src/renderer/src/App.tsx']).darwin_terminal_node).toBe(false)
})
it.each(['adhoc', 'daily', 'hourly', 'release'])(
'builds both %s runtime architectures after installing build dependencies',
(channel) => {
const jobs = Object.values(workflow(`${channel}-mac-build`).jobs)
const steps = jobs.find((job) =>
job.steps?.some((step) => step.name === 'Build and verify the patched terminal runtimes')
).steps
const build = steps.findIndex(
(step) => step.name === 'Build and verify the patched terminal runtimes'
)
expect(build).toBeGreaterThan(steps.findIndex((step) => step.name === 'Install dependencies'))
expect(steps[build].run).toContain('pnpm run build:packages')
expect(steps[build].run).toContain('verify-darwin-terminal-node.mjs --arch=arm64')
expect(steps[build].run).toContain('verify-darwin-terminal-node.mjs --arch=x64')
const packaging = steps.findIndex((step) => step.with?.command?.includes('--runtime=electron'))
expect(build).toBeLessThan(packaging)
const cache = steps.find((step) => step.with?.key?.startsWith('orca-node-runtime-mac-'))
for (const dependency of [
'pinned-node-downloads.mjs',
'update-node-runtime-pin.mjs',
'macos-terminal-host-bundle.cjs'
]) {
expect(cache.with.key).toContain(dependency)
}
if (channel === 'daily') {
expect(steps[build].if).toBe("steps.freshness.outputs.should_build == 'true'")
}
if (channel === 'adhoc' || channel === 'release') {
// Older refs and tags have no source-build recipe and must keep the pinned download.
expect(steps[build].if).toBe(
"hashFiles('config/scripts/build-darwin-terminal-node.mjs') != ''"
)
expect(steps[packaging].with.command).toMatch(
/^if \[ ! -f config\/scripts\/build-darwin-terminal-node\.mjs \]; then node .*pinned-node-downloads\.mjs darwin-arm64 darwin-x64; fi && /
)
}
}
)
it.skipIf(process.platform === 'win32').each([
['success', 'true', 0],
['failure', 'true', 1],
['cancelled', 'true', 1],
['skipped', 'true', 1],
['', 'true', 1],
['skipped', 'false', 0]
])('aggregate handles runtime result %s (required=%s)', async (result, required, code) => {
const gate = workflow('pr').jobs.verify.steps.find(
(step) => step.name === 'Require successful checks'
)
const env = Object.fromEntries(
Object.keys(gate.env).map((key) => [key, key.endsWith('_SHOULD_RUN') ? 'true' : 'success'])
)
const checked = await runProcess({
program: 'bash',
args: ['-c', gate.run],
env: {
...process.env,
...env,
SHOULD_RUN: 'true',
DARWIN_TERMINAL_NODE: result,
DARWIN_TERMINAL_NODE_SHOULD_RUN: required
},
timeoutMs: 10_000
})
expect(checked.code).toBe(code)
})
it('classifies runtime changes from the dependency-free CI sparse checkout', async () => {
const root = join(import.meta.dirname, '../..')
const directory = mkdtempSync(join(tmpdir(), 'orca-runtime-sparse-checkout-'))
try {
const checkout = workflow('pr').jobs.code_paths.steps.find(
(step) => step.with?.['sparse-checkout']
)
for (const entry of checkout.with['sparse-checkout'].trim().split('\n')) {
const path = entry.trim().replace(/^\//, '')
const source = join(root, path)
if (statSync(source).isFile()) {
const destination = join(directory, path)
mkdirSync(dirname(destination), { recursive: true })
copyFileSync(source, destination)
}
}
const result = await runProcess({
program: process.execPath,
args: ['config/scripts/pr-code-change-scope.mjs'],
cwd: directory,
input: 'config/node-runtime-patches/darwin-worker-wait.patch\n',
timeoutMs: 10_000
})
expect(result.code, result.stderr).toBe(0)
expect(result.stdout).toContain('darwin_terminal_node=true')
expect(result.stdout).toContain('package_windows=false')
} finally {
rmSync(directory, { recursive: true, force: true })
}
})
it('selects the requested runtime architecture and retains the native default', () => {
expect(readDarwinVerificationArch([], 'arm64')).toBe('arm64')
expect(readDarwinVerificationArch(['--arch=x64'], 'arm64')).toBe('x64')
expect(readDarwinVerificationArch(['--arch=arm64'], 'x64')).toBe('arm64')
})
it.each([['--arch=riscv64'], ['--arch'], ['--unknown'], ['--arch=x64', '--arch=arm64']])(
'rejects invalid verifier arguments %j before building a runtime',
(...args) => {
expect(() => readDarwinVerificationArch(args, 'arm64')).toThrow('Usage:')
}
)
@@ -1,82 +0,0 @@
const { setTimeout: delay } = require('node:timers/promises')
const fs = require('node:fs/promises')
const pty = require(process.argv[3])
const canary = require(process.argv[2])
const target = 30_000_000
const began = Date.now()
const terminal = pty.spawn(
'/bin/sh',
['-c', 'while IFS= read -r line; do printf "ECHO:%s\\n" "$line"; done'],
{ cwd: process.argv[4], env: process.env }
)
let output = ''
terminal.onData((chunk) => {
output = (output + chunk).slice(-4096)
})
const deadline = setTimeout(() => {
console.error('Stress run exceeded one hour')
terminal.kill()
process.kill(process.pid, 'SIGKILL')
}, 60 * 60_000)
async function echo(marker, timeoutMs = 10_000) {
terminal.write(`${marker}\r`)
const echoDeadline = Date.now() + timeoutMs
while (Date.now() < echoDeadline) {
if (output.includes(`ECHO:${marker}`)) {
return
}
await delay(10)
}
throw new Error(`No PTY echo for ${marker}`)
}
async function main() {
await echo('ORCA_BEFORE')
canary.startStress()
let previous = 0
let lastProgress = Date.now()
let lastPrint = 0
while (canary.stressProgress() < target) {
await delay(100)
const completed = canary.stressProgress()
if (completed !== previous) {
previous = completed
lastProgress = Date.now()
}
if (Date.now() - lastPrint > 10_000) {
console.log(JSON.stringify({ elapsedMs: Date.now() - began, completed }))
lastPrint = Date.now()
}
if (Date.now() - lastProgress > 3000) {
const marker = `ORCA_AFTER_STALL_${completed}`
await Promise.race([
Promise.all([
...Array.from({ length: 4 }, () => fs.stat(process.execPath)),
echo(marker, 40_000),
(async () => {
while (canary.stressProgress() <= completed) {
await delay(100)
}
})()
]),
delay(40_000, undefined, { ref: false }).then(() => {
throw new Error(`Workers stranded after ${completed} jobs: I/O recovery timed out`)
})
])
lastProgress = Date.now()
}
}
await fs.stat(process.execPath)
await echo('ORCA_AFTER')
console.log(JSON.stringify({ result: 'passed', arch: process.arch, completed: target }))
clearTimeout(deadline)
terminal.kill()
}
main().catch((error) => {
console.error(error)
terminal.kill()
// Stalled libuv cleanup can block process.exit().
process.kill(process.pid, 'SIGKILL')
})
@@ -294,10 +294,10 @@ async function loadNodeRuntimePin() {
}
async function pinnedDarwinNode(arch) {
const { ensureDarwinTerminalNode } = await import(
pathToFileURL(resolve(__dirname, 'build-darwin-terminal-node.mjs')).href
const { ensurePinnedNodeExecutable } = await import(
pathToFileURL(resolve(__dirname, 'pinned-node-downloads.mjs')).href
)
return ensureDarwinTerminalNode({ arch })
return ensurePinnedNodeExecutable({ target: `darwin-${arch}` })
}
/**
@@ -145,7 +145,6 @@ function fixture(detectorStep = detector) {
for (const file of [
'package.json',
'config/scripts/pr-code-change-scope.mjs',
'config/scripts/pr-darwin-terminal-node-route.mjs',
'config/scripts/pr-package-test-paths.mjs',
'config/scripts/pr-layout-oracle-route.mjs',
'config/scripts/mobile-release-check-scope.mjs',
@@ -141,7 +141,7 @@ describe('Electron runtime package contract', () => {
expect(releaseWorkflow.jobs['publish-release'].needs).toContain('build-mac')
expect(releaseWorkflow.jobs['publish-release'].needs).toContain('verify-mac-build')
const macVerification = releaseWorkflow.jobs['verify-mac-build']
expect(macVerification.needs).toEqual(['cut', 'build-mac'])
expect(macVerification.needs).toEqual(['cut', 'build-mac', 'build'])
expect(macVerification.if).toContain('!cancelled()')
expect(macVerification.if).not.toContain('needs.build.result')
expect(macVerification.permissions).toEqual({ actions: 'read', contents: 'read' })
@@ -383,7 +383,6 @@ function createMobileCiScopeRepository(
for (const file of [
'package.json',
'config/scripts/pr-code-change-scope.mjs',
'config/scripts/pr-darwin-terminal-node-route.mjs',
'config/scripts/pr-package-test-paths.mjs',
'config/scripts/pr-layout-oracle-route.mjs',
'config/scripts/git-pull-request-diff-base.mjs',
+20 -20
View File
@@ -1,7 +1,6 @@
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { isDarwinTerminalNodeSource } from './pr-darwin-terminal-node-route.mjs'
import { isLayoutOracleSource } from './pr-layout-oracle-route.mjs'
import {
isCloudTestRunnerInput,
@@ -38,7 +37,6 @@ export const PR_CHECK_JOBS = [
'managed_hook_node18',
'package',
'package_windows',
'darwin_terminal_node',
'layout_oracle',
'dependency_isolation'
]
@@ -559,26 +557,10 @@ export function classifyPrJobs(
}
}
const FILE_ROUTED_JOBS = new Map([
['git_compatibility', (file) => matchesPrefix(file, GIT_COMPAT_PREFIXES)],
['xterm_patch_sync', (file) => matchesPrefix(file, XTERM_PREFIXES)],
['shell_contracts', (file) => matchesPrefix(file, SHELL_PREFIXES)],
['orcad_browser', (file) => matchesPrefix(file, ORCAD_BROWSER_PREFIXES)],
['cross-version-wire', (file) => matchesPrefix(file, CROSS_VERSION_WIRE_PREFIXES)],
['managed_hook_node18', (file) => matchesPrefix(file, MANAGED_HOOK_PREFIXES)],
['package', isLinuxPackagePath],
['package_windows', isWindowsPackagePath],
['layout_oracle', isLayoutOracleSource],
['dependency_isolation', (file) => matchesPrefix(file, DEPENDENCY_ISOLATION_PREFIXES)],
['darwin_terminal_node', isDarwinTerminalNodeSource]
])
function jobDetector(job) {
const routesFile = FILE_ROUTED_JOBS.get(job)
if (routesFile) {
return (files) => files.some(routesFile)
}
switch (job) {
case 'git_compatibility':
return (files) => files.some((file) => matchesPrefix(file, GIT_COMPAT_PREFIXES))
case 'codex_index_heal_contract':
return (files) =>
files.some(
@@ -586,9 +568,27 @@ function jobDetector(job) {
!/^src\/main\/codex\/codex-app-server-(client|session)\.test\.ts(?!.)/s.test(file) &&
matchesPrefix(file, CODEX_INDEX_HEAL_CONTRACT_PREFIXES)
)
case 'xterm_patch_sync':
return (files) => files.some((file) => matchesPrefix(file, XTERM_PREFIXES))
case 'shell_contracts':
return (files) => files.some((file) => matchesPrefix(file, SHELL_PREFIXES))
case 'orcad_browser':
return (files) => files.some((file) => matchesPrefix(file, ORCAD_BROWSER_PREFIXES))
// Handle inside map too: default detector otherwise runs this job on every desktop-relevant PR.
case 'mobile_web_app':
return changesMobileWebApp
case 'cross-version-wire':
return (files) => files.some((file) => matchesPrefix(file, CROSS_VERSION_WIRE_PREFIXES))
case 'managed_hook_node18':
return (files) => files.some((file) => matchesPrefix(file, MANAGED_HOOK_PREFIXES))
case 'package':
return (files) => files.some(isLinuxPackagePath)
case 'package_windows':
return (files) => files.some(isWindowsPackagePath)
case 'layout_oracle':
return (files) => files.some(isLayoutOracleSource)
case 'dependency_isolation':
return (files) => files.some((file) => matchesPrefix(file, DEPENDENCY_ISOLATION_PREFIXES))
default:
return () => true
}
@@ -1,20 +0,0 @@
const DARWIN_TERMINAL_NODE_PREFIXES = [
'config/node-runtime-patches/',
'config/scripts/build-darwin-terminal-node',
'config/scripts/verify-darwin-terminal-node',
'config/scripts/darwin-terminal-node-',
'config/scripts/darwin-terminal-worker-stress',
'config/scripts/pr-darwin-terminal-node-route',
'config/scripts/__fixtures__/darwin-terminal-worker-wait',
'config/scripts/__fixtures__/darwin-threadpool-wait',
'config/scripts/macos-terminal-host-bundle',
'config/scripts/pinned-node-downloads',
'config/scripts/update-node-runtime-pin',
'config/scripts/orcad-prebuild-node-gyp',
'config/scripts/orcad-prebuild-slot-contents',
'src/shared/node-runtime-pin.ts'
]
export function isDarwinTerminalNodeSource(file) {
return DARWIN_TERMINAL_NODE_PREFIXES.some((prefix) => file.startsWith(prefix))
}
@@ -164,7 +164,7 @@ describe('release mac build workflow dispatch', () => {
expect(options.releaseRunId).toBe('987')
expect(options.workflow).toBe('release-mac-build.yml')
expect(options.pollSeconds).toBe(30)
expect(options.timeoutMinutes).toBe(390)
expect(options.timeoutMinutes).toBe(90)
})
})
@@ -195,84 +195,6 @@ const splitRun = {
}
describe('release mac build split dispatch and verification', () => {
it('hands a pending exact run to verification after observing 180 minutes without renewing its deadline', async () => {
const startedAt = Date.parse('2026-06-30T12:00:00Z')
let currentTime = startedAt
const live = { ...splitRun, status: 'in_progress', conclusion: null }
const { fetch } = createGitHubFetch([
jsonResponse(200, { workflow_run_id: 123 }),
jsonResponse(200, live),
jsonResponse(200, live),
jsonResponse(200, live)
])
const appendOutput = vi.fn()
const result = await executeReleaseMacBuildMode(
'dispatch',
{ ...splitEnvironment, RELEASE_MAC_BUILD_OBSERVE_MINUTES: '180' },
{
fetch,
appendOutput,
now: () => currentTime,
sleep: async () => {
currentTime += 180 * 60_000
}
}
)
expect(result).toMatchObject({ id: 123, status: 'in_progress', conclusion: null })
expect(appendOutput).toHaveBeenCalledOnce()
expect(appendOutput.mock.calls[0][1]).toContain(
`wait_deadline_ms=${startedAt + 390 * 60_000}\n`
)
const { fetch: finalFetch } = createGitHubFetch([
jsonResponse(200, { ...splitRun, updated_at: '2026-06-30T18:00:00Z' })
])
await expect(
executeReleaseMacBuildMode(
'wait',
{
...splitEnvironment,
RELEASE_MAC_BUILD_WAIT_DEADLINE_MS: String(startedAt + 390 * 60_000)
},
{ fetch: finalFetch, now: () => startedAt + 360 * 60_000 }
)
).resolves.toMatchObject({
status: 'completed',
conclusion: 'success'
})
const { fetch: expiredFetch } = createGitHubFetch([jsonResponse(200, live)])
await expect(
executeReleaseMacBuildMode(
'wait',
{
...splitEnvironment,
RELEASE_MAC_BUILD_WAIT_DEADLINE_MS: String(startedAt + 390 * 60_000)
},
{ fetch: expiredFetch, now: () => startedAt + 391 * 60_000 }
)
).rejects.toThrow(/Timed out/)
})
it.each([
{ ...splitRun, conclusion: 'failure' },
{ ...splitRun, head_sha: 'b'.repeat(40) }
])('does not hand off failure or changed identity as pending: %j', async (observed) => {
const { fetch } = createGitHubFetch([
jsonResponse(200, { workflow_run_id: 123 }),
jsonResponse(200, splitRun),
jsonResponse(200, observed)
])
await expect(
executeReleaseMacBuildMode(
'dispatch',
{
...splitEnvironment,
RELEASE_MAC_BUILD_OBSERVE_MINUTES: '180'
},
{ fetch, appendOutput: vi.fn(), now: () => Date.parse('2026-06-30T12:00:00Z') }
)
).rejects.toThrow()
})
it('waits for the exact dispatched run metadata to become consistent', async () => {
const { fetch, requests } = createGitHubFetch([
jsonResponse(200, { workflow_run_id: 123 }),
@@ -327,7 +249,7 @@ describe('release mac build split dispatch and verification', () => {
expect(requests[0].headers['X-GitHub-Api-Version']).toBe('2026-03-10')
expect(appendOutput).toHaveBeenCalledWith(
splitEnvironment.GITHUB_OUTPUT,
`run_id=123\nworkflow_sha=${splitRun.head_sha}\nrun_attempt=1\nwait_deadline_ms=${Date.parse('2026-06-30T18:30:00Z')}\n`
`run_id=123\nworkflow_sha=${splitRun.head_sha}\nrun_attempt=1\nwait_deadline_ms=${splitEnvironment.RELEASE_MAC_BUILD_WAIT_DEADLINE_MS}\n`
)
})
@@ -5,7 +5,6 @@ import { runInNewContext } from 'node:vm'
import { runProcess } from '@orca/process-host'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
import { readReleaseMacBuildWorkflowOptions } from './run-release-mac-build-workflow.mjs'
const { jobs } = parse(readFileSync('.github/workflows/release-cut.yml', 'utf8'))
const { steps } = jobs.build
@@ -36,39 +35,12 @@ function signingAllowed(submission, outcome, attempt = 1, platform = 'win', uplo
}
describe('Windows release signing waits for macOS success', () => {
it('covers the cold runtime and packaging budget in two hosted observation jobs', () => {
const child = parse(readFileSync('.github/workflows/release-mac-build.yml', 'utf8')).jobs[
'build-mac'
]
const dispatch = jobs['build-mac'].steps.find((step) => step.id === 'dispatch')
const options = readReleaseMacBuildWorkflowOptions({
GITHUB_REPOSITORY: 'stablyai/orca',
GITHUB_RUN_ID: '1',
GITHUB_TOKEN: 'test',
RELEASE_MAC_BUILD_REF: 'main',
RELEASE_MAC_BUILD_TAG: 'v1.2.3',
...dispatch.env
})
const observationMinutes = Number(dispatch.env.RELEASE_MAC_BUILD_OBSERVE_MINUTES)
expect(child['timeout-minutes']).toBe(360)
expect(options.timeoutMinutes).toBeGreaterThanOrEqual(child['timeout-minutes'] + 30)
expect(observationMinutes).toBe(180)
expect(jobs['build-mac']['timeout-minutes']).toBeGreaterThan(observationMinutes)
expect(jobs['verify-mac-build']['timeout-minutes']).toBeGreaterThan(
options.timeoutMinutes - observationMinutes
)
for (const job of [jobs['build-mac'], jobs['verify-mac-build'], jobs.build]) {
expect(job['timeout-minutes']).toBeLessThanOrEqual(360)
}
})
it('waits outside the Windows runner, then verifies the exact macOS run before signing', () => {
it('packages concurrently, then verifies the exact dispatched macOS run before signing', () => {
expect(jobs.build.needs).toContain('build-mac')
expect(jobs.build.if).toContain("needs.build-mac.result == 'success'")
expect(jobs.build.needs).toContain('verify-mac-build')
expect(jobs.build.if).toContain("needs.verify-mac-build.result == 'success'")
expect(jobs.build.needs).not.toContain('verify-mac-build')
expect(jobs['build-mac'].needs).not.toContain('build')
expect(jobs['verify-mac-build'].needs).not.toContain('build')
expect(jobs['verify-mac-build'].needs).toContain('build')
const windows = jobs.build.strategy.matrix.include.find((entry) => entry.platform === 'win')
expect(windows.release_command).toMatch(
/^node config\/scripts\/ensure-native-runtime\.mjs --runtime=electron; if \(\$LASTEXITCODE -ne 0\) \{ exit \$LASTEXITCODE \}; pnpm exec electron-builder /
@@ -2,7 +2,7 @@ import { appendFileSync } from 'node:fs'
import { pathToFileURL } from 'node:url'
const DEFAULT_API_VERSION = '2026-03-10'
const DEFAULT_TIMEOUT_MINUTES = 390
const DEFAULT_TIMEOUT_MINUTES = 90
const DEFAULT_POLL_SECONDS = 30
const RUN_DISCOVERY_TIMEOUT_SECONDS = 120
@@ -100,11 +100,6 @@ export async function finishReleaseMacBuildWorkflow(options, workflowRunId, deps
const api = createGitHubApiClient(options, deps)
const completedRun = await waitForReleaseMacBuildRun(api, workflowRunId, options, deps)
if (completedRun.status !== 'completed' && deps.observationDeadlineMs != null) {
console.log(`Mac release build workflow remains pending: ${completedRun.html_url}`)
return completedRun
}
if (completedRun.conclusion !== 'success') {
throw new Error(
`Mac release build workflow ${completedRun.html_url ?? completedRun.id} concluded ${
@@ -195,13 +190,9 @@ export async function waitForReleaseMacBuildRun(api, workflowRunId, options, dep
}
return run
}
const observedAtMs = now()
if (observedAtMs > deadlineMs) {
if (now() > deadlineMs) {
break
}
if (deps.observationDeadlineMs != null && observedAtMs >= deps.observationDeadlineMs) {
return run
}
console.log(
`Mac release build workflow is ${run.status}; polling again in ${options.pollSeconds}s`
@@ -319,15 +310,6 @@ export async function executeReleaseMacBuildMode(mode, env = process.env, deps =
output,
`run_id=${run.id}\nworkflow_sha=${run.head_sha}\nrun_attempt=${run.run_attempt}\nwait_deadline_ms=${deadlineMs}\n`
)
if (env.RELEASE_MAC_BUILD_OBSERVE_MINUTES) {
const observationMinutes = readPositiveInteger(env.RELEASE_MAC_BUILD_OBSERVE_MINUTES, 180)
return await finishReleaseMacBuildWorkflow(options, run.id, {
...deps,
deadlineMs,
observationDeadlineMs: now() + observationMinutes * 60 * 1000,
expectedIdentity: { id: run.id, headSha: run.head_sha, attempt: run.run_attempt }
})
}
return run
}
if (mode === 'wait') {
@@ -1,118 +0,0 @@
import { cpSync, mkdtempSync, rmSync } from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { runProcess, runProcessSync } from '@orca/process-host'
import { ensureDarwinTerminalNode } from './build-darwin-terminal-node.mjs'
import { preparePinnedNodeDir } from './pinned-node-downloads.mjs'
import { nodeGypRebuild, stageNodeAddonApi } from './orcad-prebuild-node-gyp.mjs'
import { isDirectInvocation } from './script-entry-detection.mjs'
import { NODE_RUNTIME_PIN } from '../../src/shared/node-runtime-pin.ts'
export function readDarwinVerificationArch(args, defaultArch = process.arch) {
if (args.length > 1 || (args.length === 1 && !/^--arch=(arm64|x64)$/.test(args[0]))) {
throw new Error('Usage: verify-darwin-terminal-node.mjs [--arch=arm64|x64]')
}
const arch = args.length ? args[0].slice('--arch='.length) : defaultArch
if (arch !== 'arm64' && arch !== 'x64') {
throw new Error(`Unsupported macOS terminal Node architecture: ${arch}`)
}
return arch
}
async function verifyDarwinTerminalNode(arch) {
if (process.platform !== 'darwin') {
throw new Error('Verify the Darwin terminal runtime on a macOS host')
}
const executable = await ensureDarwinTerminalNode({ arch })
const scratch = mkdtempSync(join(tmpdir(), 'orca-darwin-terminal-node-proof-'))
const require = createRequire(import.meta.url)
try {
const headers = await preparePinnedNodeDir({
target: `darwin-${arch}`,
workDir: join(scratch, 'headers')
})
const sourcePty = dirname(require.resolve('node-pty/package.json'))
const stagedPty = join(scratch, 'node-pty')
for (const entry of ['package.json', 'binding.gyp', 'src', 'lib']) {
cpSync(join(sourcePty, entry), join(stagedPty, entry), { recursive: true })
}
stageNodeAddonApi(sourcePty, stagedPty)
await nodeGypRebuild({
stagedDir: stagedPty,
workDir: scratch,
nodeDir: headers,
runProcess: (options) =>
runProcessSync({
...options,
program: executable,
timeoutMs: 120_000,
env: { ...options.env, ORCA_BACKGROUND_LAUNCH: '1', npm_config_arch: arch }
})
})
const addon = join(scratch, 'worker-wait.node')
const compiled = await runProcess({
program: 'clang',
args: [
'-arch',
arch === 'x64' ? 'x86_64' : 'arm64',
'-O2',
'-shared',
'-fPIC',
'-undefined',
'dynamic_lookup',
'-I',
join(headers, 'include/node'),
join(import.meta.dirname, '__fixtures__/darwin-terminal-worker-wait.c'),
'-o',
addon
],
timeoutMs: 60_000
})
if (compiled.code !== 0) {
throw new Error(compiled.stderr)
}
const result = await runProcess({
program: executable,
args: [
join(import.meta.dirname, 'darwin-terminal-node-probe.cjs'),
addon,
stagedPty,
scratch,
`v${NODE_RUNTIME_PIN.version}`
],
env: { ...process.env, ORCA_BACKGROUND_LAUNCH: '1', UV_THREADPOOL_SIZE: '4' },
timeoutMs: 60_000
})
if (result.code !== 0) {
throw new Error(`Runtime proof failed (${result.code}): ${result.stderr}`)
}
if (JSON.parse(result.stdout.trim()).arch !== arch) {
throw new Error(`Runtime proof used the wrong architecture; expected ${arch}`)
}
console.log(result.stdout.trim())
if (process.env.ORCA_DARWIN_WORKER_STRESS === '1') {
const stress = await runProcess({
program: executable,
args: [
join(import.meta.dirname, 'darwin-terminal-worker-stress.cjs'),
addon,
stagedPty,
scratch
],
env: { ...process.env, ORCA_BACKGROUND_LAUNCH: '1', UV_THREADPOOL_SIZE: '4' },
timeoutMs: 61 * 60_000
})
console.log(stress.stdout.trim())
if (stress.code !== 0) {
throw new Error(`Sequential worker stress failed: ${stress.stderr}`)
}
}
} finally {
rmSync(scratch, { recursive: true, force: true })
}
}
if (isDirectInvocation(import.meta.url, process.argv[1])) {
await verifyDarwinTerminalNode(readDarwinVerificationArch(process.argv.slice(2)))
}
@@ -149,35 +149,22 @@ export function requiresEnvOptIn(source) {
})
}
// Treat any runs-on label/array/group/matrix that may be Windows as a lane; simple matrix axes resolve
// from their declared values and an unresolvable expression fails closed for review.
export function couldRunOnWindows(runsOn, matrix = {}) {
// Treat any runs-on label/array/group/matrix that may be Windows as a lane; unresolvable expression fails closed for review.
export function couldRunOnWindows(runsOn) {
const labels =
typeof runsOn === 'string'
? [runsOn]
: Array.isArray(runsOn)
? runsOn
: [...(runsOn?.labels ?? []), runsOn?.group ?? ''].flat()
return labels.some((label) => {
const axis = /^\$\{\{\s*matrix\.(\w+)\s*\}\}$/.exec(String(label))?.[1]
const values = axis && matrix[axis]
if (Array.isArray(values) && values.length > 0) {
const included = Array.isArray(matrix.include)
? matrix.include.flatMap((entry) => (entry[axis] === undefined ? [] : [entry[axis]]))
: []
return [...values, ...included].some((value) => couldRunOnWindows(value))
}
return /windows/i.test(String(label)) || String(label).includes('${{')
})
return labels.some((label) => /windows/i.test(String(label)) || String(label).includes('${{'))
}
/** The Windows job's curated files: the boundary list plus any argv the lane steps spell out. */
function readWindowsWorkflow() {
const workflow = parse(readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8'))
const jobs = Object.entries(workflow.jobs ?? {})
const windowsJobs = jobs.filter(([, job]) =>
couldRunOnWindows(job?.['runs-on'], job?.strategy?.matrix)
)
const windowsJobs = jobs.filter(([, job]) => couldRunOnWindows(job?.['runs-on']))
const steps = workflow.jobs?.[WINDOWS_LANE_JOB]?.steps ?? []
const runs = WINDOWS_LANE_STEPS.map((name) => {
const step = steps.find((candidate) => candidate?.name === name)
@@ -410,21 +397,6 @@ describe('manual opt-in classification', () => {
})
describe('Windows runner detection', () => {
it('resolves explicit matrix axes while keeping unknown expressions conservative', () => {
expect(couldRunOnWindows('${{ matrix.os }}', { os: ['macos-15', 'macos-15-intel'] })).toBe(
false
)
expect(couldRunOnWindows('${{ matrix.os }}', { os: ['macos-15', 'windows-2022'] })).toBe(true)
expect(couldRunOnWindows('${{ matrix.os }}', { os: [] })).toBe(true)
expect(
couldRunOnWindows('${{ matrix.os }}', {
os: ['macos-15'],
include: [{ os: 'windows-2022' }]
})
).toBe(true)
expect(couldRunOnWindows('${{ inputs.runner }}', { os: ['macos-15'] })).toBe(true)
})
it('reads every runs-on spelling that could land on Windows', () => {
expect(couldRunOnWindows('windows-2022')).toBe(true)
// The spelling that would have slipped past an equality test.
@@ -1,181 +0,0 @@
# macOS terminal daemon worker wakeup stall
On 2026-10-15, a long-running packaged macOS daemon accepted RPC traffic and PTY
output but stopped delivering terminal input or creating terminals. Its Node
24.21.0/libuv 1.52.1 event loop remained responsive. All four worker threads were
idle in `__psynch_cvwait`, with 38 queued jobs and no completed work waiting for
the loop. Submitting one asynchronous stat grew the queue to 39 without a callback.
This distinguishes the incident from a busy or saturated worker pool.
A read-only native snapshot found these pthread condition generations:
```text
L = U = 0x953f6000
S = 0x953f5c02
worker wait L: 0x153f6000, 0x953f6000, 0x953f5f00, 0x953f5e00
```
The oldest waiter was exactly `0x80000000` behind the current generation. This
matches the psynch half-range failure discussed in
[libuv #5280](https://github.com/libuv/libuv/issues/5280). Kernel waiter ordering
can turn wake signals into preposts; once the signal generation U catches L,
subsequent signals can return in userspace while workers remain asleep.
## Recurrence on 2026-10-23
A preserved v50 daemon running the unpatched Node 24.21.0/libuv 1.52.1
runtime stopped accepting input with 155 sessions. Authenticated control requests
still answered in 2–3 ms and terminal output continued; new terminal creation
timed out after 30 seconds. Asynchronous stat, directory and crypto callbacks
stalled, while all four workers slept in `__psynch_cvwait`, the global work queue
was nonempty and the completion queue was empty. The existing detector logged
`async-io-stalled`. A working Claude terminal belonged to a different, older
daemon, so the apparent Codex/Claude distinction was a daemon ownership difference.
A one-off native diagnostic interrupted all four verified workers while holding
their pool mutex. Queued work drained, fresh stats completed in about 1 ms, and
input resumed with the same PTY incarnation and agent processes. The user
confirmed recovery. Inspector access was then closed. This validates recovery
without restarting agents, but private-memory access and signal injection are
not production recovery mechanisms; bounded worker waits are the runtime fix.
## Reproduction and limits
The [upstream candidate](https://github.com/libuv/libuv/pull/5351) includes a
manual witness using production libuv workers and their real queue. It parks one
worker, advances the condition through 8,388,605 timed waits on the **same mutex**,
enrolls three fresh workers, and submits four jobs. It changes no private pthread
counters and does not occupy the worker pool with blocking filesystem requests.
| Build | Jobs | Callbacks | Queue | Idle workers |
| ------------------------------ | ---- | --------- | -------- | ------------ |
| Unpatched, half-range boundary | 0 | 0 | Nonempty | 4 |
| Patched, same aging witness | 4 | 4 | Empty | 4 |
| Short control, either build | 4 | 4 | Empty | 4 |
The unpatched witness produced L = U = 2147483904, S = 2147482882, and oldest
wait L = 256: exactly the same half-range relationship as the live daemon.
The patched witness refreshed the old wait during aging and completed normally.
A separate idle-generation regression fails on unpatched libuv and passes with
the patch; nineteen focused pool, cancellation, fork and condition tests pass.
The controlled witness proves the native mechanism; the later sequential-job
reproduction below establishes an ordinary scheduling path to it. The precise
Orca job-producer sequence is still unproven. Stress runs with up to 60 million jobs, stop/continue cycles,
signals and PTY churn did not independently reproduce that enrollment sequence.
The earlier PTY fault injection established symptoms only.
## Ordinary-workload reproduction
A later run on the exact unpatched packaged Node also reproduced the failure
without condition aging or fault injection: queue one empty native job, and queue
its successor from the completion callback. Four workers service one outstanding
job. An initial run stalled after 11,032,951 completed jobs and hung in worker
cleanup on normal exit. A second instrumented run started with a successful real
PTY echo and stalled after 11,886,884 completed jobs:
```text
L = 3043043584; oldest worker wait L = 895559936
L - oldest wait L = 2147483648 = 0x80000000
four idle workers; one queued native job
```
Four fresh async stats and a PTY write produced zero stat callbacks and no PTY
echo after 15 seconds; six jobs remained queued and U caught L. One worker had
stayed parked while millions of sequential jobs ran through the others. This
provides an ordinary scheduling path to the exact live-daemon signature. An
otherwise similar 30-million-job run completed, so the scheduling is intermittent.
The patched runtime completed all 30 million jobs, a fresh filesystem request
and real PTY echo. This still does not identify the exact sequence of Orca's own
job producers.
The repository now includes a portable-to-both-macOS-architectures N-API workload
and PTY oracle with no private pthread fields. Run it after the normal check:
```sh
ORCA_BACKGROUND_LAUNCH=1 ORCA_DARWIN_WORKER_STRESS=1 node config/scripts/verify-darwin-terminal-node.mjs
```
The workload runs 30 million sequential native jobs and requires PTY echo before
and after. After three seconds without progress, it allows forty seconds for
the thirty-second worker timeout and scheduling margin, requiring all four fresh
stat callbacks, a new PTY echo and resumed native work. It kills only its own disposable test
process if libuv cleanup is stranded. This probabilistic stress is opt-in; the
idle-refresh regression remains the regular CI oracle.
## Runtime mitigation
Only the private Darwin worker wait changes: it uses a 30-second relative timed
condition wait, retaining the existing queue predicate and Darwin error workaround.
Workers still wake immediately when signaled. An idle timeout rechecks queued
work and refreshes the wait generation, allowing recovery from lost wakeups.
The default four-worker pool adds at most eight timeout wakes per minute; the
regression also caps generation advancement to reject an accidental wake loop.
Public condition variables and other platforms are unchanged.
`config/scripts/build-darwin-terminal-node.mjs` builds the pinned official Node
source with this patch. The recipe now follows Node 24.22.1: its source archive
matches the official SHA-256 manifest, the patch applies with zero fuzz, and the
reviewed patched worker source is byte-identical to the Node 24.21.0 recipe. The recipe pins the source archive hash, reviewed patched
source hash, architecture and minimum OS. Cache receipts verify the resulting
executable bytes and fingerprint the build scripts, so changes to build logic
invalidate cached binaries. A Node pin change fails until the recipe is reviewed.
This preserves the existing signed
helper layout and stable host copy rather than changing its launch or permissions.
Run the shipping-runtime check on each macOS architecture:
```sh
ORCA_BACKGROUND_LAUNCH=1 node config/scripts/verify-darwin-terminal-node.mjs
```
It builds the runtime, checks real PTY input and filesystem/DNS/crypto operations,
observes all four idle worker generations refresh, bounds their advancement, and
checks fresh asynchronous work. It inspects only its own test process. The Node 24.21.0 recipe passed on Apple silicon (x64 under Rosetta) and on
physical ARM and Intel CI ([run](https://github.com/stablyai/orca/actions/runs/38795005777)).
On 2026-10-23, the repository-built Node 24.22.1 arm64 runtime also passed:
```json
{
"node": "v24.22.1",
"uv": "1.52.1",
"arch": "arm64",
"fsDnsCrypto": "passed",
"ptyInput": "passed",
"idleWaitRefresh": "passed"
}
```
The official unpatched 24.22.1 runtime passed the same initial PTY and I/O checks,
then failed the idle-generation `advances > 0n` assertion. This supplies a current-pin
red/green regression. ARM CI qualification passed on PR commit `6f006d865193` in
[run 39922624279](https://github.com/stablyai/orca/actions/runs/39922624279);
Intel qualification was still running at review time. The cold
build also exposed multi-minute synchronous source cleanup; the cache lease is
now released after atomic publication and before cleanup, so a blocked heartbeat
cannot let a waiting builder steal the completed producer's lock. Cold source compilation is
substantial and is cached separately from packaging retries.
## Coverage and remaining risks
The packaged macOS terminal daemon receives this runtime, including local folder
workspaces and worktrees, and clients attached to that daemon. Existing daemons
keep their executable until deliberately restarted. The detection/restart prompt
is tracked separately in [Orca #29087](https://github.com/stablyai/orca/pull/29087).
No automatic restart or queued-input replay is part of this patch.
Linux/Windows and WSL are unaffected by the Darwin-only change. Standalone/SSH
server runtimes and the development Electron fallback do not receive this patch.
Mobile/relay clients benefit only when their execution host uses the patched
helper. An isolated Developer ID signed copy of the installed helper, with the
repository-built runtime substituted, served a PTY under hardened runtime. Its
protocol 45 required a matching smoke-test handshake; the original patch checkout used 46. A
repeat on October 23 with Node 24.22.1 passed strict signature verification,
real PTY input, filesystem/DNS/crypto and all four idle worker refreshes under
hardened runtime. This disposable copy used the installed helper's native
dependencies and an untimestamped Developer ID signature; it does not establish
notarization or qualification of a complete current package. A complete package
build, launchd/TCC attribution, older macOS releases, battery
impact and extended soak remain verification gaps. Do not infer remote process
death from missing callbacks or loss of contact. The experimental reliability gate records these
limits; an upstream merge and a Node release can eventually replace this backport.
@@ -109,9 +109,8 @@ The helper declares no links, documents or types, has a lower `CFBundleVersion`
launchd leaves no record, but another process running the copy's executable directly can make
macOS register it, so retirement runs `lsregister -u` on the bundle before deleting it.
The original helper used the official Node UUID, shared by that release and architecture.
The worker-wakeup backport builds Node from source and has a different UUID; its launchd/TCC
attribution still needs verification. Local Network access passed in the original signed spike:
The helper's main-executable UUID is Node's, shared by every official Node 24.21.0 binary of that
architecture, so it is not unique to Orca. Local Network access still passed in the signed spike:
macOS matched the client by its code signature, and the job's `AssociatedBundleIdentifiers` ties it
to Orca. Do not register copies explicitly (`lsregister -f` or `LSRegisterURL`).
@@ -3,7 +3,7 @@ import { PREVIOUS_DAEMON_PROTOCOL_VERSIONS, PROTOCOL_VERSION } from './types'
describe('foreground-confirmation daemon protocol', () => {
it('rejects daemons from before the fresh-confirmation RPC', () => {
expect(PROTOCOL_VERSION).toBe(52)
expect(PROTOCOL_VERSION).toBe(51)
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(19)
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(22)
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(23)
@@ -23,6 +23,6 @@ describe('foreground-confirmation daemon protocol', () => {
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(37)
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(38)
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(39)
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(51)
expect(PREVIOUS_DAEMON_PROTOCOL_VERSIONS).toContain(50)
})
})
+2 -3
View File
@@ -1,5 +1,4 @@
// Why: daemons survive app updates, so wire behavior must be version-gated.
// v52 starts new terminals on a fresh daemon whose patched Node recovers lost macOS worker wake-ups.
// v51 checks terminal identity, ownership and attachments before reserving teardown.
// v50 makes the `codex` function keep Orca's terminal identity under a filtering shell_environment_policy.
// v49 makes the `claude` function follow the selection over the user's own CLAUDE_CONFIG_DIR.
@@ -10,7 +9,7 @@
// v43 dropped it with the revert); v41 stages long startup commands as sourced scripts; v40 rolled
// the #25130/#24636 shell-wrapper changes and the wider agent list into a fresh daemon; older owners
// stay attachable.
export const PROTOCOL_VERSION = 52
export const PROTOCOL_VERSION = 51
export const FENCED_TERMINATION_PROTOCOL_VERSION = 51
export const CLAUDE_ACCOUNT_FUNCTION_DAEMON_PROTOCOL_VERSION = 42
export const CLAUDE_ACCOUNT_FUNCTION_REVERTED_DAEMON_PROTOCOL_VERSION = 43
@@ -45,7 +44,7 @@ export const CLEAN_DISCONNECT_PROTOCOL_VERSION = 24
export const MODE_2031_UNSUBSCRIBE_FACT_PROTOCOL_VERSION = 29
export const PREVIOUS_DAEMON_PROTOCOL_VERSIONS = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50
] as const
export function supportsColorQueryReplyColors(protocolVersion: number): boolean {
@@ -3,10 +3,10 @@
"appId": "com.stablyai.orca",
"stateSchemaVersion": 1,
"readableStateSchemaVersions": [1],
"daemonProtocolVersion": 52,
"daemonProtocolVersion": 51,
"attachableDaemonProtocolVersions": [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52
51
]
}
@@ -3,10 +3,10 @@ export const LOCAL_BUILD_COMPATIBILITY_CONTRACT = {
appId: 'com.stablyai.orca',
stateSchemaVersion: 1,
readableStateSchemaVersions: [1],
daemonProtocolVersion: 52,
daemonProtocolVersion: 51,
attachableDaemonProtocolVersions: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52
51
]
} as const
+1 -1
View File
@@ -72,7 +72,7 @@ export const daemonAdoptedSchema = z
export const DAEMON_ASYNC_IO_STALL_DURATION_BUCKETS = ['<1m', '1-5m', '5-30m', '30m+'] as const
// Why: measures whether #29133's runtime patch makes daemon worker-pool stalls recover.
// Why: measures how often daemon worker-pool stalls start and how long they last before clearing.
const daemonAsyncIoHealthBase = {
protocol_generation: z.number().int().positive().max(1_000),
app_version_match: z.enum(DAEMON_ADOPTED_APP_VERSION_MATCH)