authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-03 16:30:31-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-03 16:30:31-07:00
logf25e4428961982e3935300cfd3c78f7d0fac4ee9
tree687a5f4e7de18a420cb93486f62abc1339f9ff51
parent498ab9c399d5e8c3097a286be14d03238e071ac1
signature Signed by PGP key B5690EEEBB952194

fix(antigravity): allow slow runtime startup during setup (#9510)


7 files changed, 107 insertions(+), 8 deletions(-)

apps/server/src/provider/AntigravityAuth.test.ts+42-1
......@@ -56,6 +56,7 @@ const makeHarness = Effect.fn("makeAuthTestHarness")(function* (
5656 readonly interactive?: boolean;
5757 readonly authorizationUrls?: ReadonlyArray<string>;
5858 readonly supportsLogout?: boolean;
59 readonly beforeInitialize?: Effect.Effect<void>;
5960 readonly forwardCallback?: Effect.Effect<void, ProviderSetupError>;
6061 } = {},
6162) {
......@@ -82,8 +83,9 @@ const makeHarness = Effect.fn("makeAuthTestHarness")(function* (
8283 );
8384 return {
8485 initialize: () =>
85 Effect.sync(() => {
86 Effect.gen(function* () {
8687 events.push("initialize");
88 yield* options.beforeInitialize ?? Effect.void;
8789 return options.supportsLogout === false
8890 ? { ...initialized, agentCapabilities: {} }
8991 : initialized;
......@@ -394,6 +396,45 @@ it.layer(NodeServices.layer)("AntigravityAuth", (it) => {
394396 }),
395397 );
396398
399 it.effect("signs out after a slow packaged runtime starts", () =>
400 Effect.gen(function* () {
401 const entered = yield* Deferred.make<void>();
402 const initialized = yield* Deferred.make<void>();
403 const harness = yield* makeHarness({
404 beforeInitialize: Deferred.succeed(entered, undefined).pipe(
405 Effect.andThen(Deferred.await(initialized)),
406 ),
407 });
408 const logout = yield* harness.auth.controller.logout(Effect.void).pipe(Effect.forkScoped);
409 yield* Deferred.await(entered);
410 yield* TestClock.adjust("47 seconds");
411 yield* Deferred.succeed(initialized, undefined);
412 assert.equal((yield* Fiber.join(logout)).phase, "idle");
413 assert.include(harness.events, "logout");
414 assert.deepEqual(harness.catalog(), []);
415 yield* Deferred.await(harness.closed);
416 }),
417 );
418
419 it.effect("closes a stalled sign-out process without clearing its account catalog", () =>
420 Effect.gen(function* () {
421 const entered = yield* Deferred.make<void>();
422 const harness = yield* makeHarness({
423 beforeInitialize: Deferred.succeed(entered, undefined).pipe(Effect.andThen(Effect.never)),
424 });
425 const logout = yield* harness.auth.controller
426 .logout(Effect.void)
427 .pipe(Effect.exit, Effect.forkScoped);
428 yield* Deferred.await(entered);
429 yield* TestClock.adjust("90 seconds");
430 assert.isTrue(Exit.isFailure(yield* Fiber.join(logout)));
431 yield* Deferred.await(harness.closed);
432 assert.notInclude(harness.events, "logout");
433 assert.deepEqual(harness.catalog(), ["previous-account-model"]);
434 yield* harness.auth.withProcess(Effect.void, Effect.void).pipe(Effect.scoped);
435 }),
436 );
437
397438 it.effect(
398439 "sign-out interrupts startup without interrupting its caller after startup returns",
399440 () =>
apps/server/src/provider/AntigravityAuth.ts+1-1
......@@ -480,7 +480,7 @@ export const makeAntigravityAuth = Effect.fn("makeAntigravityAuth")(function* <
480480 }).pipe(
481481 Effect.scoped,
482482 Effect.timeoutOrElse({
483 duration: "30 seconds",
483 duration: "90 seconds",
484484 orElse: () => Effect.fail(setupError("logout", "Antigravity sign-out timed out.")),
485485 }),
486486 ),
apps/server/src/provider/Drivers/AntigravityDriver.test.ts+25-1
......@@ -11,10 +11,13 @@ import {
1111 HostProcessPlatform,
1212} from "@t3tools/shared/hostProcess";
1313import * as Effect from "effect/Effect";
14import * as Deferred from "effect/Deferred";
15import * as Fiber from "effect/Fiber";
1416import * as FileSystem from "effect/FileSystem";
1517import * as Layer from "effect/Layer";
1618import * as Path from "effect/Path";
1719import * as Schema from "effect/Schema";
20import * as TestClock from "effect/testing/TestClock";
1821import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
1922
2023import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
......@@ -108,7 +111,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
108111 const first = yield* makeExecutable("runtime 'one");
109112 const second = yield* makeExecutable("runtime two");
110113 const signedOut = yield* makeExecutable("runtime signed-out", true);
111 const controls = { selected: first, failResolution: false };
114 const controls = { selected: first, failResolution: false, beforeAcquire: Effect.void };
112115 const acquisitions: Array<{ binaryPath: string | undefined; path: string | undefined }> = [];
113116 const releases: Array<string | null> = [];
114117 const launches: Array<{
......@@ -129,6 +132,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
129132 acquire: (binaryPath, environment) =>
130133 Effect.gen(function* () {
131134 acquisitions.push({ binaryPath, path: environment?.PATH });
135 yield* controls.beforeAcquire;
132136 if (controls.failResolution) {
133137 return yield* new AntigravityInstallationError({
134138 operation: "resolve",
......@@ -249,6 +253,26 @@ it.layer(testLayer)("AntigravityDriver", (it) => {
249253 }).pipe(Effect.scoped),
250254 );
251255
256 it.effect.skipIf(windowsHost)("refreshes models after slow process startup", () =>
257 Effect.gen(function* () {
258 const h = yield* makeHarness();
259 const entered = yield* Deferred.make<void>();
260 const ready = yield* Deferred.make<void>();
261 h.controls.beforeAcquire = Deferred.succeed(entered, undefined).pipe(
262 Effect.andThen(Deferred.await(ready)),
263 );
264 const refresh = yield* h.refresh().pipe(Effect.forkScoped);
265 yield* Deferred.await(entered);
266 yield* TestClock.adjust("70 seconds");
267 yield* Deferred.succeed(ready, undefined);
268 yield* Fiber.join(refresh);
269 const snapshot = yield* h.instance.snapshot.getSnapshot;
270 expect(snapshot.auth.status).toBe("authenticated");
271 expect(snapshot.models.length).toBeGreaterThan(0);
272 yield* h.assertClosed;
273 }).pipe(Effect.scoped),
274 );
275
252276 it.effect.skipIf(windowsHost)(
253277 "refreshes a disabled instance through the selected executable and personal Google ACP",
254278 () =>
apps/server/src/provider/Drivers/AntigravityDriver.ts+1-1
......@@ -332,7 +332,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
332332 },
333333 Effect.scoped,
334334 Effect.timeoutOrElse({
335 duration: "60 seconds",
335 duration: "90 seconds",
336336 orElse: () =>
337337 Effect.fail(
338338 new ProviderDriverError({
apps/server/src/provider/Layers/AntigravityProvider.test.ts+34-3
......@@ -417,11 +417,17 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {
417417 Effect.gen(function* () {
418418 const harness = yield* makeHarness();
419419 yield* harness.initialize;
420 yield* Ref.set(harness.probe, Effect.sleep("20 seconds").pipe(Effect.as(initializeResult)));
420 const entered = yield* Deferred.make<void>();
421 const initialized = yield* Deferred.make<EffectAcpSchema.InitializeResponse>();
422 yield* Ref.set(
423 harness.probe,
424 Deferred.succeed(entered, undefined).pipe(Effect.andThen(Deferred.await(initialized))),
425 );
421426
422427 const refresh = yield* harness.provider.snapshot.refresh.pipe(Effect.forkChild);
423 yield* Effect.yieldNow;
424 yield* TestClock.adjust("20 seconds");
428 yield* Deferred.await(entered);
429 yield* TestClock.adjust("47 seconds");
430 yield* Deferred.succeed(initialized, initializeResult);
425431 const snapshot = yield* Fiber.join(refresh);
426432
427433 expect(snapshot).toMatchObject({
......@@ -433,6 +439,31 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {
433439 ),
434440 );
435441
442 it.effect("closes a stalled health probe at its deadline", () =>
443 Effect.scoped(
444 Effect.gen(function* () {
445 const harness = yield* makeHarness();
446 yield* harness.initialize;
447 const entered = yield* Deferred.make<void>();
448 const closed = yield* Deferred.make<void>();
449 yield* Ref.set(
450 harness.probe,
451 Deferred.succeed(entered, undefined).pipe(
452 Effect.andThen(Effect.never),
453 Effect.ensuring(Deferred.succeed(closed, undefined)),
454 ),
455 );
456 const refresh = yield* harness.provider.snapshot.refresh.pipe(Effect.forkChild);
457 yield* Deferred.await(entered);
458 yield* TestClock.adjust("90 seconds");
459 const snapshot = yield* Fiber.join(refresh);
460 yield* Deferred.await(closed);
461 expect(snapshot.status).toBe("error");
462 expect(snapshot.message).toContain("90 seconds");
463 }),
464 ),
465 );
466
436467 it.effect("distinguishes missing executables from a failed installed executable", () =>
437468 Effect.scoped(
438469 Effect.gen(function* () {
apps/server/src/provider/Layers/AntigravityProvider.ts+1-1
......@@ -32,7 +32,7 @@ import {
3232
3333const EMPTY_MODEL_CAPABILITIES = createModelCapabilities({ optionDescriptors: [] });
3434const MAX_WORKSPACE_SNAPSHOTS = 32;
35const HEALTH_CHECK_TIMEOUT = "45 seconds";
35const HEALTH_CHECK_TIMEOUT = "90 seconds";
3636const SIGN_IN_MESSAGE = "Sign in with Google to use Antigravity.";
3737const AUTH_UNCHECKED_MESSAGE =
3838 "Antigravity is installed. Google account access is not checked yet.";
docs/user/providers-antigravity.md+3
......@@ -169,6 +169,9 @@ in web or desktop provider settings, or **Refresh models** in the mobile model p
169169uses saved Google sign-in and does not open a login page. If sign-in is required, use the
170170provider's setup controls. Automatic status checks verify the installation only.
171171
172The packaged runtime can be slow to start, especially on Windows. Health checks, model refresh,
173and sign-out each allow up to 90 seconds before reporting a timeout.
174
172175If Google reports `SUBSCRIPTION_REQUIRED`, an account restriction, or a usage limit, read the
173176provider's message. A finished turn can contain an upstream error instead of completed work.
174177Use any retry time Google supplies. T3 Code does not switch to an API key to get past the limit.