Skip to content

Commit 7ac1e2c

Browse files
fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build (#398)
* fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build Two Windows-install fixes. 1. Add electron-squirrel-startup handling. main.ts had no handler for the --squirrel-{install,updated,uninstall,obsolete} flags Squirrel runs the exe with during install/update/uninstall. Without it the install hook booted the full app (window + daemon spawn) and never exited, so the installer hung waiting on it. Now we create/remove shortcuts and quit immediately; it is a no-op on macOS/Linux. 2. Add a Windows CI smoke-install step. After the build, run Setup.exe --silent on the clean native x64 windows-latest runner, assert the install dir is created, and upload SquirrelSetup.log as an artifact. This captures the install log we otherwise cannot get and gives a build-vs-host verdict: a clean install proves the artifact is good, so a failing user machine is host-side (AV/disk/signing). The runner has no real-time AV, so it does NOT prove SmartScreen/Defender will accept the unsigned binaries on end-user machines; code-signing stays the durable fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(windows): capture SquirrelSetup.log from the app dir, fix misleading warning Update.exe writes SquirrelSetup.log into %LocalAppData%\AgentOrchestrator, not SquirrelTemp; the smoke step looked only in SquirrelTemp and so printed "failed before Update.exe ran" even on a successful install (exit 0, dir created). Look in the app root dir first, fall back to SquirrelTemp, and drop the false-failure wording. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 47e3ddd commit 7ac1e2c

5 files changed

Lines changed: 89 additions & 0 deletions

File tree

.github/workflows/testing-build.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,46 @@ jobs:
5858
# `npm run make` keeps the premake daemon build; --targets restricts to this
5959
# platform's maker.
6060
run: npm run make -- --targets ${{ matrix.target }}
61+
# Smoke-install the Squirrel installer on a clean, native x64 Windows runner.
62+
# This is a build-vs-host verdict: if it installs here it proves the artifact
63+
# is good and a failing user machine is host-side (AV/disk/signing); if it
64+
# fails here the build/Squirrel config is wrong. continue-on-error so a failed
65+
# install never blocks publishing the artifacts. The runner has no real-time
66+
# AV blocking, so a clean install here does NOT prove SmartScreen/Defender
67+
# won't reject the unsigned binaries on end-user machines.
68+
- name: Smoke-install the Windows installer
69+
if: runner.os == 'Windows'
70+
continue-on-error: true
71+
timeout-minutes: 5
72+
shell: pwsh
73+
run: |
74+
$setup = Get-ChildItem -Path out/make/squirrel.windows -Recurse -Filter '*Setup.exe' | Select-Object -First 1
75+
if (-not $setup) { Write-Host '::error::no Setup.exe produced under out/make/squirrel.windows'; exit 1 }
76+
Write-Host "Running $($setup.FullName) --silent"
77+
$proc = Start-Process -FilePath $setup.FullName -ArgumentList '--silent' -PassThru -Wait
78+
Write-Host "Setup.exe exit code: $($proc.ExitCode)"
79+
$installDir = Join-Path $env:LOCALAPPDATA 'AgentOrchestrator'
80+
# Update.exe writes SquirrelSetup.log into the app root dir; the Setup.exe
81+
# bootstrapper only logs to SquirrelTemp for the earliest extraction stage.
82+
# Grab whichever exists and copy it into the workspace for upload.
83+
$log = @(
84+
(Join-Path $installDir 'SquirrelSetup.log'),
85+
(Join-Path $env:LOCALAPPDATA 'SquirrelTemp\SquirrelSetup.log')
86+
) | Where-Object { Test-Path $_ } | Select-Object -First 1
87+
if ($log) { Copy-Item $log squirrel-setup.log; Write-Host "captured log: $log" } else { Write-Host '::warning::no SquirrelSetup.log found in install dir or SquirrelTemp' }
88+
if (Test-Path $installDir) {
89+
Write-Host "INSTALL OK: $installDir created"
90+
Get-ChildItem $installDir | Select-Object Name | Format-Table -AutoSize
91+
} else {
92+
Write-Host "::warning::INSTALL FAILED: $installDir was not created"
93+
}
94+
- name: Upload SquirrelSetup.log
95+
if: runner.os == 'Windows'
96+
uses: actions/upload-artifact@v4
97+
with:
98+
name: squirrel-setup-log-${{ github.sha }}
99+
path: frontend/squirrel-setup.log
100+
if-no-files-found: warn
61101
- name: Publish to a 0.0.0-testing-<sha> prerelease
62102
shell: bash
63103
env:

frontend/package-lock.json

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"@xterm/xterm": "^5.5.0",
6969
"class-variance-authority": "^0.7.1",
7070
"clsx": "^2.1.1",
71+
"electron-squirrel-startup": "^1.0.1",
7172
"lucide-react": "^1.17.0",
7273
"openapi-fetch": "^0.17.0",
7374
"posthog-js": "^1.390.2",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// electron-squirrel-startup ships no types. Its default export is the boolean
2+
// result of handling any --squirrel-* startup event (true when the app should
3+
// quit, false on macOS/Linux or a normal launch).
4+
declare module "electron-squirrel-startup" {
5+
const startup: boolean;
6+
export default startup;
7+
}

frontend/src/main.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type OpenDialogOptions,
1313
} from "electron";
1414
import { updateElectronApp } from "update-electron-app";
15+
import squirrelStartup from "electron-squirrel-startup";
1516
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
1617
import { existsSync } from "node:fs";
1718
import { readFile } from "node:fs/promises";
@@ -48,6 +49,18 @@ const ignoreStdStreamError = (err: NodeJS.ErrnoException): void => {
4849
process.stdout.on("error", ignoreStdStreamError);
4950
process.stderr.on("error", ignoreStdStreamError);
5051

52+
// Windows Squirrel runs this exe with a --squirrel-{install,updated,uninstall,
53+
// obsolete} flag during install/update/uninstall. electron-squirrel-startup
54+
// creates/removes the shortcuts for that event and returns true; we then quit
55+
// immediately instead of booting the full app. A full launch (window + daemon
56+
// spawn) would hang the installer, which runs this hook and waits for it to
57+
// exit — that hang is the Squirrel-side failure. Returns false on macOS/Linux,
58+
// so this is a no-op there.
59+
const isSquirrelStartup: boolean = squirrelStartup;
60+
if (isSquirrelStartup) {
61+
app.quit();
62+
}
63+
5164
// Must run before app ready so the About panel and default-menu role labels use it.
5265
app.setName("Agent Orchestrator");
5366

@@ -678,6 +691,9 @@ function initAutoUpdates(): void {
678691
}
679692

680693
app.whenReady().then(() => {
694+
// A Squirrel install/update hook already called app.quit() above; do no
695+
// startup work (no window, no daemon spawn) so the hook exits promptly.
696+
if (isSquirrelStartup) return;
681697
registerRendererProtocol();
682698
createWindow();
683699
void startDaemon();

0 commit comments

Comments
 (0)