From 9ee0b7f35d03142a085c8ee8c880feaee3322e79 Mon Sep 17 00:00:00 2001 From: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Date: Fri, 18 Sep 2026 23:16:33 -0700 Subject: [PATCH] fix(server): surface SIGILL/AVX detail when Antigravity runtime fails to start Motivation: On CPUs that lack AVX/AVX2, the Antigravity runtime binary crashes on startup with SIGILL during post-install validation. The server reports one fixed generic message ("The downloaded Antigravity runtime could not start in this environment.") for every validation failure, so users mistake the illegal-instruction crash for archive corruption or a permissions/WSL issue and repeatedly re-download the 682 MB archive with no way to self-diagnose. Approach: In AntigravityInstallation.ts, the validate effect's error mapping now inspects the failure's cause chain for the signal that killed the child process (effect/platform's node child-process spawner raises "Process interrupted due to receipt of signal: ''" when a process exits via signal, which is preserved through PlatformError and AcpTransportError's cause fields). When that signal is SIGILL, the detail message now names the illegal-instruction crash and points at missing AVX/AVX2 CPU support as the likely cause; every other failure keeps the original generic message unchanged. This does not attempt a CPU-flag pre-flight check (the issue's other proposed option); it only makes the existing failure legible instead of adding a new check. Validation: `npx vp test run src/provider/AntigravityInstallation.validationFailure.test.ts src/provider/AntigravityInstallation.test.ts` (run from apps/server) - the new test file's 7 tests (signal extraction, chained-cause traversal, no-signal fallback, non-Error input, cyclical-cause bound, and both branches of the detail message) all pass, and all 29 previously-passing tests in AntigravityInstallation.test.ts still pass. One pre-existing, unrelated test in that file fails on this machine due to macOS resolving /tmp to /private/tmp; confirmed pre-existing by stashing this change and reproducing the identical failure before restoring it. Also ran `npx vp run typecheck` (apps/server) - exit 0, no new diagnostics - and `npx vp lint --report-unused-disable-directives` on the touched files - clean. Report: https://github.com/pingdotgg/t3code/issues/11414 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Assisted-by: claude-sonnet-5 (via Claude Code) --- .../src/provider/AntigravityInstallation.ts | 36 +++++++++-- ...vityInstallation.validationFailure.test.ts | 60 +++++++++++++++++++ 2 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 apps/server/src/provider/AntigravityInstallation.validationFailure.test.ts diff --git a/apps/server/src/provider/AntigravityInstallation.ts b/apps/server/src/provider/AntigravityInstallation.ts index 24eb4e3d6df8..66094c7afa75 100644 --- a/apps/server/src/provider/AntigravityInstallation.ts +++ b/apps/server/src/provider/AntigravityInstallation.ts @@ -136,6 +136,33 @@ const installationError = (operation: string, detail: string, cause?: unknown) = const wrapFailure = (operation: string, detail: string) => (cause: unknown) => isInstallationError(cause) ? cause : installationError(operation, detail, cause); +const SIGNAL_INTERRUPT_PATTERN = /receipt of signal: '([A-Za-z0-9]+)'/; + +/** + * Walks a chained `cause` (as produced by effect/platform's child process + * spawner) looking for the signal that killed the process. Bounded so an + * unexpected cyclical cause cannot recurse forever. + * @internal + */ +export function findTerminatingSignal(cause: unknown, depth = 0): string | undefined { + if (depth > 8 || !(cause instanceof Error)) return undefined; + const match = SIGNAL_INTERRUPT_PATTERN.exec(cause.message); + if (match) return match[1]; + return findTerminatingSignal((cause as { cause?: unknown }).cause, depth + 1); +} + +const GENERIC_VALIDATION_FAILURE_DETAIL = + "The downloaded Antigravity runtime could not start in this environment."; +const SIGILL_VALIDATION_FAILURE_DETAIL = + "The downloaded Antigravity runtime crashed on startup with an illegal instruction (SIGILL). This usually means the CPU is missing instructions (such as AVX/AVX2) that the Antigravity runtime requires."; + +/** @internal */ +export function describeValidationFailure(cause: unknown): string { + return findTerminatingSignal(cause) === "SIGILL" + ? SIGILL_VALIDATION_FAILURE_DETAIL + : GENERIC_VALIDATION_FAILURE_DETAIL; +} + function executableNames(platform: NodeJS.Platform) { return platform === "win32" ? { executable: "agy_acp_server.exe", harness: "localharness_external.exe" } @@ -508,11 +535,10 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma Effect.provideService(Path.Path, path), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(Crypto.Crypto, crypto), - Effect.mapError( - wrapFailure( - "verify", - "The downloaded Antigravity runtime could not start in this environment.", - ), + Effect.mapError((cause) => + isInstallationError(cause) + ? cause + : installationError("verify", describeValidationFailure(cause), cause), ), ); diff --git a/apps/server/src/provider/AntigravityInstallation.validationFailure.test.ts b/apps/server/src/provider/AntigravityInstallation.validationFailure.test.ts new file mode 100644 index 000000000000..e489c3113111 --- /dev/null +++ b/apps/server/src/provider/AntigravityInstallation.validationFailure.test.ts @@ -0,0 +1,60 @@ +import * as NodeAssert from "node:assert/strict"; + +import { describe, it } from "vite-plus/test"; + +import { describeValidationFailure, findTerminatingSignal } from "./AntigravityInstallation.ts"; + +describe("findTerminatingSignal", () => { + it("extracts the signal name from a child-process interrupt error", () => { + const cause = new Error("Process interrupted due to receipt of signal: 'SIGILL'"); + NodeAssert.equal(findTerminatingSignal(cause), "SIGILL"); + }); + + it("walks a chain of wrapped causes to find the signal", () => { + const signalError = new Error("Process interrupted due to receipt of signal: 'SIGILL'"); + const platformError = new Error("Unknown: ChildProcess.exitCode (agy_acp_server.par)", { + cause: signalError, + }); + const transportError = new Error("ACP transport operation read-process-exit-status failed.", { + cause: platformError, + }); + NodeAssert.equal(findTerminatingSignal(transportError), "SIGILL"); + }); + + it("returns undefined when no signal is present in the chain", () => { + const cause = new Error("The archive could not be read.", { + cause: new Error("ENOENT: no such file or directory"), + }); + NodeAssert.equal(findTerminatingSignal(cause), undefined); + }); + + it("returns undefined for non-error causes", () => { + NodeAssert.equal(findTerminatingSignal(undefined), undefined); + NodeAssert.equal(findTerminatingSignal("SIGILL"), undefined); + }); + + it("does not recurse forever on a cyclical cause chain", () => { + const cause: Error & { cause?: unknown } = new Error("boom"); + cause.cause = cause; + NodeAssert.equal(findTerminatingSignal(cause), undefined); + }); +}); + +describe("describeValidationFailure", () => { + it("names AVX/AVX2 as the likely cause when the runtime is killed by SIGILL", () => { + const cause = new Error("ACP transport operation read-process-exit-status failed.", { + cause: new Error("Process interrupted due to receipt of signal: 'SIGILL'"), + }); + const detail = describeValidationFailure(cause); + NodeAssert.match(detail, /SIGILL/); + NodeAssert.match(detail, /AVX/); + }); + + it("falls back to the generic message for other failures", () => { + const cause = new Error("The downloaded runtime did not identify as the expected release."); + NodeAssert.equal( + describeValidationFailure(cause), + "The downloaded Antigravity runtime could not start in this environment.", + ); + }); +});