diff --git a/.github/workflows/release-desktop-smoke.yml b/.github/workflows/release-desktop-smoke.yml index 670f9895..113f1c01 100644 --- a/.github/workflows/release-desktop-smoke.yml +++ b/.github/workflows/release-desktop-smoke.yml @@ -186,7 +186,7 @@ jobs: build-windows-electron: if: ${{ inputs.build_windows }} - name: Build Windows Electron (x64) + name: Build Windows Electron (${{ matrix.arch }}) # Match the production release workflow. windows-latest currently resolves # to a runner with Visual Studio 18, which this Electron/node-gyp stack does # not detect correctly. @@ -198,6 +198,9 @@ jobs: - arch: x64 target: x86_64-pc-windows-msvc platform: win32-x64 + - arch: arm64 + target: aarch64-pc-windows-msvc + platform: win32-arm64 steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -249,6 +252,9 @@ jobs: - name: Rebuild native modules working-directory: packages/electron shell: bash + env: + # Cross-compile for ARM64 target from x64 runner. + ELECTRON_BUILDER_ARCH: ${{ matrix.arch }} # npmRebuild=false in package.json, so electron-builder won't # recompile native deps on its own. Rebuild against the target # Electron ABI before packaging, matching the release workflow. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa826eb9..47dbb0c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -284,6 +284,9 @@ jobs: - arch: x64 target: x86_64-pc-windows-msvc platform: win32-x64 + - arch: arm64 + target: aarch64-pc-windows-msvc + platform: win32-arm64 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -331,6 +334,9 @@ jobs: - name: Rebuild native modules working-directory: packages/electron shell: bash + env: + # Cross-compile for ARM64 target from x64 runner. + ELECTRON_BUILDER_ARCH: ${{ matrix.arch }} # npmRebuild=false in package.json, so electron-builder won't # recompile native deps on its own — we must rebuild against the # target Electron ABI before packaging. @@ -350,7 +356,6 @@ jobs: files: | packages/electron/dist/*.exe packages/electron/dist/*.blockmap - packages/electron/dist/latest.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -504,7 +509,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} combine-electron-manifests: - needs: [create-release, build-desktop-electron-macos] + needs: [create-release, build-desktop-electron-macos, build-desktop-electron-windows] runs-on: ubuntu-latest steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -514,13 +519,13 @@ jobs: with: node-version: '22' - - name: Download per-arch latest-mac.yml + - name: Download per-arch update manifests uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: - pattern: latest-yml-*-apple-darwin + pattern: latest-yml-* path: artifacts - - name: Finalize combined latest-mac.yml + - name: Finalize combined manifests env: LATEST_YML_DIR: ${{ github.workspace }}/artifacts GH_REPO: ${{ github.repository }} @@ -533,6 +538,8 @@ jobs: tag_name: v${{ needs.create-release.outputs.version }} files: | ${{ runner.temp }}/latest-mac.yml + ${{ runner.temp }}/latest.yml + ${{ runner.temp }}/latest-arm64.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/electron/README.md b/packages/electron/README.md index b218565e..4d97ab15 100644 --- a/packages/electron/README.md +++ b/packages/electron/README.md @@ -76,7 +76,7 @@ macOS builds produce `dmg` and `zip` artifacts. Windows builds produce an NSIS i macOS packaging needs Xcode/build tools for notarized builds and icon asset compilation. -Windows packaging needs NSIS support through `electron-builder`. If no Windows signing env is set, `package.mjs` disables code signing and builds an unsigned installer. +Windows packaging needs NSIS support through `electron-builder`. If no Windows signing env is set, `package.mjs` disables code signing and builds an unsigned installer. Windows updates use `latest.yml` for x64 and the `latest-arm64.yml` channel for ARM64 so each installation resolves an architecture-matching installer. Linux AppImages must be built natively. Set `OPENCHAMBER_TARGET_ARCH=x64` or `OPENCHAMBER_TARGET_ARCH=arm64` when packaging; the build rejects a target that does not match the Linux host. The same target selects the bundled OpenCode CLI, native Electron rebuild, and Electron Builder architecture. Linux identity is stable across architectures: executable `openchamber`, desktop file `openchamber.desktop`, icon `openchamber`, and `StartupWMClass=openchamber`. diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index ba41017b..eb202147 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -16,6 +16,7 @@ import { resolveManagedOpenCodeCwd } from './opencode-cwd.mjs'; import { sanitizeRuntimeRequestHeaders } from './runtime-request-headers.mjs'; import { assertUpdaterCapability } from './updater-capability.mjs'; import { checkForDesktopUpdate } from './updater-check.mjs'; +import { resolveUpdaterChannel } from './updater-channel.mjs'; import { resolveUpdaterFeed } from './updater-feed.mjs'; import { buildLinuxInstalledApps, @@ -2927,10 +2928,17 @@ const setupAutoUpdater = () => { const testBuild = typeof __OPENCHAMBER_UPDATER_E2E_BUILD__ !== 'undefined' && __OPENCHAMBER_UPDATER_E2E_BUILD__ === true; const feed = resolveUpdaterFeed({ testBuild }); + const updaterChannel = feed.provider === 'github' + ? resolveUpdaterChannel({ platform: process.platform, architecture: process.arch }) + : null; + if (updaterChannel) { + autoUpdater.channel = updaterChannel; + } autoUpdater.setFeedURL(feed); log.info('[electron] updater feed configured', { provider: feed.provider, target: feed.provider === 'github' ? `${feed.owner}/${feed.repo}` : feed.url, + channel: updaterChannel || 'latest', }); autoUpdater.on('download-progress', (progress) => { diff --git a/packages/electron/package.json b/packages/electron/package.json index 49ece813..026e86cb 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -40,7 +40,7 @@ "generate:macos-icon": "node ./scripts/generate-macos-icon-assets.cjs", "rebuild:native": "node ./scripts/rebuild-native.mjs", "test:architecture": "node --test ./scripts/target-architecture.test.mjs ./scripts/verify-linux-appimage.test.mjs ./scripts/verify-update-manifest.test.mjs", - "test:updater": "node --test ./updater-capability.test.mjs ./updater-check.test.mjs ./updater-feed.test.mjs ./scripts/updater-e2e-fixture.test.mjs", + "test:updater": "node --test ./updater-capability.test.mjs ./updater-channel.test.mjs ./updater-check.test.mjs ./updater-feed.test.mjs ./scripts/finalize-latest-yml.test.mjs ./scripts/updater-e2e-fixture.test.mjs", "test:linux-desktop": "node --test ./linux-autostart.test.mjs && node ./scripts/smoke-linux-app-discovery.mjs && node ./scripts/smoke-path-open-utils.mjs", "updater:e2e:fixture": "node ./scripts/updater-e2e-fixture.mjs", "verify:update-manifest": "node ./scripts/verify-update-manifest.mjs", diff --git a/packages/electron/scripts/finalize-latest-yml.mjs b/packages/electron/scripts/finalize-latest-yml.mjs index 2b657a92..46c690fd 100644 --- a/packages/electron/scripts/finalize-latest-yml.mjs +++ b/packages/electron/scripts/finalize-latest-yml.mjs @@ -76,14 +76,11 @@ const output = {}; const winX64 = await read('latest-yml-x86_64-pc-windows-msvc', 'latest.yml'); const winArm64 = await read('latest-yml-aarch64-pc-windows-msvc', 'latest.yml'); -if (winX64 || winArm64) { - const base = winArm64 || winX64; - output['latest.yml'] = serialize({ - version: base.version, - files: [...(winArm64?.files || []), ...(winX64?.files || [])], - releaseDate: base.releaseDate, - }); +if (!winX64 || !winArm64) { + throw new Error('Both x64 and arm64 Windows update manifests are required'); } +output['latest.yml'] = serialize(winX64); +output['latest-arm64.yml'] = serialize(winArm64); const macX64 = await read('latest-yml-x86_64-apple-darwin', 'latest-mac.yml'); const macArm64 = await read('latest-yml-aarch64-apple-darwin', 'latest-mac.yml'); diff --git a/packages/electron/scripts/finalize-latest-yml.test.mjs b/packages/electron/scripts/finalize-latest-yml.test.mjs new file mode 100644 index 00000000..0a8f39ca --- /dev/null +++ b/packages/electron/scripts/finalize-latest-yml.test.mjs @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict'; +import { execFileSync, spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const script = fileURLToPath(new URL('./finalize-latest-yml.mjs', import.meta.url)); + +const manifest = (architecture) => `version: 1.2.3 +files: + - url: OpenChamber-1.2.3-win-${architecture}.exe + sha512: ${architecture}-checksum + size: 123 +releaseDate: '2026-07-30T00:00:00.000Z' +`; + +const createFixture = ({ includeArm64 = true } = {}) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-latest-yml-')); + const artifacts = path.join(root, 'artifacts'); + const output = path.join(root, 'output'); + fs.mkdirSync(path.join(artifacts, 'latest-yml-x86_64-pc-windows-msvc'), { recursive: true }); + fs.writeFileSync(path.join(artifacts, 'latest-yml-x86_64-pc-windows-msvc', 'latest.yml'), manifest('x64')); + if (includeArm64) { + fs.mkdirSync(path.join(artifacts, 'latest-yml-aarch64-pc-windows-msvc'), { recursive: true }); + fs.writeFileSync(path.join(artifacts, 'latest-yml-aarch64-pc-windows-msvc', 'latest.yml'), manifest('arm64')); + } + fs.mkdirSync(output); + return { root, artifacts, output }; +}; + +const environment = ({ artifacts, output }) => ({ + ...process.env, + LATEST_YML_DIR: artifacts, + RUNNER_TEMP: output, + GH_REPO: 'openchamber/openchamber', + OPENCHAMBER_VERSION: '1.2.3', +}); + +test('writes separate x64 and ARM64 Windows update channels', (context) => { + const fixture = createFixture(); + context.after(() => fs.rmSync(fixture.root, { recursive: true, force: true })); + + execFileSync(process.execPath, [script], { env: environment(fixture) }); + + const x64 = fs.readFileSync(path.join(fixture.output, 'latest.yml'), 'utf8'); + const arm64 = fs.readFileSync(path.join(fixture.output, 'latest-arm64.yml'), 'utf8'); + assert.match(x64, /win-x64\.exe/); + assert.doesNotMatch(x64, /win-arm64\.exe/); + assert.match(arm64, /win-arm64\.exe/); + assert.doesNotMatch(arm64, /win-x64\.exe/); +}); + +test('fails instead of publishing an incomplete Windows channel set', (context) => { + const fixture = createFixture({ includeArm64: false }); + context.after(() => fs.rmSync(fixture.root, { recursive: true, force: true })); + + const result = spawnSync(process.execPath, [script], { env: environment(fixture), encoding: 'utf8' }); + + assert.notEqual(result.status, 0); + assert.match(result.stderr, /Both x64 and arm64 Windows update manifests are required/); +}); diff --git a/packages/electron/scripts/prepare-opencode-cli.mjs b/packages/electron/scripts/prepare-opencode-cli.mjs index 06834f0d..7abbb6c3 100644 --- a/packages/electron/scripts/prepare-opencode-cli.mjs +++ b/packages/electron/scripts/prepare-opencode-cli.mjs @@ -47,7 +47,17 @@ const artifactForPlatform = (platform, targetArchitecture) => { if (arch === 'x64') return { name: 'opencode-darwin-x64-baseline.zip', binary: 'opencode' }; } if (platform === 'win32') { - if (arch === 'arm64') return { name: 'opencode-windows-arm64.zip', binary: 'opencode.exe' }; + // TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun + // FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130). + // Bundle x64-baseline instead (runs under x64 emulation); OpenCode self-upgrade + // is disabled elsewhere so it can't overwrite with the broken ARM64 build. + // Remove this block and restore the original below when the upstream issue + // is resolved. + // --- ORIGINAL (restore when ARM64 is fixed) --- + // if (arch === 'arm64') return { name: 'opencode-windows-arm64.zip', binary: 'opencode.exe' }; + // if (arch === 'x64') return { name: 'opencode-windows-x64-baseline.zip', binary: 'opencode.exe' }; + // --- END ORIGINAL --- + if (arch === 'arm64') return { name: 'opencode-windows-x64-baseline.zip', binary: 'opencode.exe' }; if (arch === 'x64') return { name: 'opencode-windows-x64-baseline.zip', binary: 'opencode.exe' }; } if (platform === 'linux') { diff --git a/packages/electron/updater-channel.mjs b/packages/electron/updater-channel.mjs new file mode 100644 index 00000000..baa04ab5 --- /dev/null +++ b/packages/electron/updater-channel.mjs @@ -0,0 +1,3 @@ +export const resolveUpdaterChannel = ({ platform, architecture }) => ( + platform === 'win32' && architecture === 'arm64' ? 'latest-arm64' : null +); diff --git a/packages/electron/updater-channel.test.mjs b/packages/electron/updater-channel.test.mjs new file mode 100644 index 00000000..6955949d --- /dev/null +++ b/packages/electron/updater-channel.test.mjs @@ -0,0 +1,13 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { resolveUpdaterChannel } from './updater-channel.mjs'; + +test('uses an architecture-specific Windows ARM64 update channel', () => { + assert.equal(resolveUpdaterChannel({ platform: 'win32', architecture: 'arm64' }), 'latest-arm64'); +}); + +test('keeps the default channel for other desktop targets', () => { + assert.equal(resolveUpdaterChannel({ platform: 'win32', architecture: 'x64' }), null); + assert.equal(resolveUpdaterChannel({ platform: 'darwin', architecture: 'arm64' }), null); + assert.equal(resolveUpdaterChannel({ platform: 'linux', architecture: 'arm64' }), null); +}); diff --git a/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx index 77e06c50..bafba6bb 100644 --- a/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx @@ -16,6 +16,7 @@ import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore'; import { useUIStore } from '@/stores/useUIStore'; import { useI18n } from '@/lib/i18n'; import { runtimeFetch } from '@/lib/runtime-fetch'; +import { isWindowsArm64 } from '@/lib/platform'; export const OpenCodeCliSettings: React.FC = () => { const { t } = useI18n(); @@ -151,13 +152,15 @@ export const OpenCodeCliSettings: React.FC = () => { - + {!isWindowsArm64() && ( + + )} = ({ onClose, forceMobile return isDesktopShell() && typeof window !== 'undefined' && (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'linux'; }, []); + const isWindowsArm64 = React.useMemo(() => isWindowsArm64Platform(), []); // keep platform check available for future window chrome tweaks @@ -421,12 +423,12 @@ export const SettingsView: React.FC = ({ onClose, forceMobile const settingsSearchResults = React.useMemo(() => { return buildSettingsSearchResults({ query: settingsSearchQuery, - runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac, isWindows, isLinux }, + runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac, isWindows, isLinux, isWindowsArm64 }, visiblePageSlugs, t, getPageTitle, }); - }, [getPageTitle, isDesktopLocalOrigin, isMac, isWindows, isLinux, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]); + }, [getPageTitle, isWindowsArm64, isDesktopLocalOrigin, isMac, isWindows, isLinux, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]); const prepareSettingsSearchTarget = React.useCallback((result: SettingsSearchResult): string => { if (result.id.startsWith('agents.')) { diff --git a/packages/ui/src/lib/platform.ts b/packages/ui/src/lib/platform.ts index f419fa98..a6910e36 100644 --- a/packages/ui/src/lib/platform.ts +++ b/packages/ui/src/lib/platform.ts @@ -7,6 +7,39 @@ export const isCapacitorApp = (): boolean => { return capacitor?.isNativePlatform?.() === true || window.location.protocol === 'capacitor:'; }; +// TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun +// FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130). +// Suppress OpenCode update UI on ARM64 so it can't self-upgrade to the broken +// ARM64 build. Remove this helper and its call sites when resolved upstream. +export const isWindowsArm64 = (): boolean => { + if (typeof window === 'undefined') return false; + + const electronArch = window.__OPENCHAMBER_ELECTRON__?.arch?.toLowerCase?.(); + if (electronArch === 'arm64' || electronArch === 'aarch64') { + const platform = (navigator.platform || '').toLowerCase(); + return platform.includes('win'); + } + + const vscodeArch = (window as { __VSCODE_CONFIG__?: { arch?: string } }).__VSCODE_CONFIG__?.arch?.toLowerCase?.(); + if (vscodeArch === 'arm64' || vscodeArch === 'aarch64') { + const platform = (navigator.platform || '').toLowerCase(); + return platform.includes('win'); + } + + const nav = (navigator as Navigator & { userAgentData?: { architecture?: string; platform?: string } }).userAgentData; + if (nav?.architecture?.toLowerCase?.() === 'arm64' || nav?.architecture?.toLowerCase?.() === 'aarch64') { + const platform = (nav.platform || navigator.platform || '').toLowerCase(); + return platform.includes('win'); + } + + const ua = navigator.userAgent.toLowerCase(); + if ((ua.includes('aarch64') || ua.includes('arm64') || ua.includes('armv')) && ua.includes('windows')) { + return true; + } + + return false; +}; + /** * True when running inside the native Capacitor shell on an iPad. * Capacitor reports 'ios' for both iPhone and iPad; iPadOS WKWebView diff --git a/packages/ui/src/lib/settings/search.ts b/packages/ui/src/lib/settings/search.ts index 0773e9fb..33db680a 100644 --- a/packages/ui/src/lib/settings/search.ts +++ b/packages/ui/src/lib/settings/search.ts @@ -26,6 +26,8 @@ interface SettingsSearchAvailabilityContext extends SettingsRuntimeContext { isWindows: boolean; // Linux desktop shell — for controls that only render on linux. isLinux: boolean; + // Windows ARM64 — temporary workaround gate (see opencode#19130). + isWindowsArm64: boolean; } const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ @@ -470,7 +472,7 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ page: 'general', titleKey: 'settings.openchamber.opencodeCli.field.showUpdateNotifications', keywords: ['opencode', 'cli', 'updates'], - isAvailable: (ctx) => !ctx.isVSCode, + isAvailable: (ctx) => !ctx.isVSCode && !ctx.isWindowsArm64, }, { id: 'sessions.agent-control-tool', diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index 3a780be9..905dd6b1 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -10,6 +10,7 @@ import { getStoredMobileKeyboardMode, type MobileKeyboardMode } from '@/lib/mobi import { getRuntimeKey } from '@/lib/runtime-switch'; import type { TerminalShell } from '@/lib/api/types'; import { useFilesViewTabsStore } from './useFilesViewTabsStore'; +import { isWindowsArm64 } from '@/lib/platform'; export type MainTab = 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files' | 'context' | 'diagram'; export type PendingDiffScope = 'working' | 'staged' | 'turn'; @@ -999,7 +1000,7 @@ export const useUIStore = create()( showTerminalQuickKeysOnDesktop: false, persistChatDraft: true, - showOpenCodeUpdateNotifications: true, + showOpenCodeUpdateNotifications: !isWindowsArm64(), agentControlToolEnabled: true, inputSpellcheckEnabled: false, wideChatLayoutEnabled: false, diff --git a/packages/vscode/src/opencode-upgrade-runtime.ts b/packages/vscode/src/opencode-upgrade-runtime.ts index 30f60857..883957f1 100644 --- a/packages/vscode/src/opencode-upgrade-runtime.ts +++ b/packages/vscode/src/opencode-upgrade-runtime.ts @@ -1,7 +1,7 @@ type UpgradeCapability = { supported: boolean; - manager: 'opencode' | 'external' | null; - reason: 'external' | 'unavailable' | null; + manager: 'opencode' | 'external' | 'openchamber' | null; + reason: 'external' | 'unavailable' | 'windows-arm64-workaround' | null; }; export type OpenCodeUpgradeManager = { @@ -15,6 +15,12 @@ type UpgradeResult = { status: number; body: Record }; let openCodeUpgradePromise: Promise | null = null; +// TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun +// FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130). +// Disable OpenCode self-upgrade on ARM64 so it can't overwrite the working x64 +// binary with the broken ARM64 build. Remove when the upstream issue is resolved. +const isWindowsArm64 = (): boolean => process.platform === 'win32' && process.arch === 'arm64'; + const parseVersion = (value: unknown): { parts: number[]; prerelease: boolean } => { const normalized = String(value || '').replace(/^v/, '').split('+')[0]; const prereleaseIndex = normalized.indexOf('-'); @@ -39,6 +45,7 @@ const compareVersions = (left: unknown, right: unknown): number => { }; const getCapability = (manager?: OpenCodeUpgradeManager): UpgradeCapability => { + if (isWindowsArm64()) return { supported: false, manager: 'openchamber', reason: 'windows-arm64-workaround' }; if (!manager) return { supported: false, manager: null, reason: 'unavailable' }; if (manager.getDebugInfo().mode !== 'managed') return { supported: false, manager: 'external', reason: 'external' }; if (!manager.getApiUrl()) return { supported: false, manager: null, reason: 'unavailable' }; diff --git a/packages/web/server/lib/opencode/env-runtime.js b/packages/web/server/lib/opencode/env-runtime.js index 2382c706..0bd65b50 100644 --- a/packages/web/server/lib/opencode/env-runtime.js +++ b/packages/web/server/lib/opencode/env-runtime.js @@ -664,8 +664,15 @@ export const createOpenCodeEnvRuntime = (deps) => { }; const getWindowsNativeOpencodePackageNames = () => { + // TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun + // FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130). + // prepare-opencode-cli.mjs bundles x64-baseline instead; match that here so + // the runtime resolver looks for the same x64-baseline package. Restore the + // arm64 branch below when the upstream issue is resolved. if (process.arch === 'arm64') { - return ['opencode-windows-arm64']; + // --- ORIGINAL (restore when ARM64 is fixed) --- + // return ['opencode-windows-arm64']; + return ['opencode-windows-x64-baseline', 'opencode-windows-x64']; } if (process.arch === 'x64') { // Prefer the baseline build when bypassing package-manager wrappers so the diff --git a/packages/web/server/lib/opencode/upgrade-capability.js b/packages/web/server/lib/opencode/upgrade-capability.js index 80606dee..feeca99a 100644 --- a/packages/web/server/lib/opencode/upgrade-capability.js +++ b/packages/web/server/lib/opencode/upgrade-capability.js @@ -1,9 +1,23 @@ +// TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun +// FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130). +// Disable OpenCode self-upgrade on ARM64 so it can't overwrite the working x64 +// binary with the broken ARM64 build. Remove when the upstream issue is resolved. +const isWindowsArm64 = () => process.platform === 'win32' && process.arch === 'arm64'; + export const resolveOpenCodeUpgradeCapability = ({ isExternal, hasManagedProcess, activeBinary, isBundledBinary, }) => { + if (isWindowsArm64()) { + return { + supported: false, + manager: 'openchamber', + reason: 'windows-arm64-workaround', + }; + } + if (isExternal) { return { supported: false,