Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions apps/server/src/provider/AntigravityInstallation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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),
),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -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.",
);
});
});
Loading