From 562c9ef98f9d80cc2a6623d94dd69a530c76105a Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Nov 2026 04:09:30 +0000 Subject: [PATCH 1/3] test(server): reproduce #29039 Antigravity sign-in blaming the network for a hung runtime These tests reproduce #29039: when the Antigravity process never answers ACP initialize, sign-in still says it has not reached Google and later that it could not reach Google, blaming the network. The auth test harness now runs its own initialize inside start(), as the real AcpSessionRuntime does, so beforeInitialize can hang the process before authenticate. Co-Authored-By: Claude Opus 5.5 --- .../src/provider/AntigravityAuth.test.ts | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/apps/server/src/provider/AntigravityAuth.test.ts b/apps/server/src/provider/AntigravityAuth.test.ts index af156a61663f4..60bc601135af8 100644 --- a/apps/server/src/provider/AntigravityAuth.test.ts +++ b/apps/server/src/provider/AntigravityAuth.test.ts @@ -82,17 +82,19 @@ const makeHarness = Effect.fn("makeAuthTestHarness")(function* ( yield* Deferred.succeed(closed, undefined); }), ); + const initialize = () => + Effect.gen(function* () { + events.push("initialize"); + yield* options.beforeInitialize ?? Effect.void; + return options.supportsLogout === false + ? { ...initialized, agentCapabilities: {} } + : initialized; + }); return { - initialize: () => - Effect.gen(function* () { - events.push("initialize"); - yield* options.beforeInitialize ?? Effect.void; - return options.supportsLogout === false - ? { ...initialized, agentCapabilities: {} } - : initialized; - }), + initialize, start: () => Effect.gen(function* () { + yield* initialize(); events.push("authenticate"); yield* Deferred.succeed(authenticateCalled, undefined); if (options.interactive !== false && input.onAuthorizationUrl) { @@ -278,6 +280,35 @@ it.layer(NodeServices.layer)("AntigravityAuth", (it) => { }), ); + it.effect("does not blame the network when the agent never answers initialize", () => + Effect.gen(function* () { + const initializing = yield* Deferred.make(); + const harness = yield* makeHarness({ + beforeInitialize: Deferred.succeed(initializing, undefined).pipe( + Effect.andThen(Effect.never), + ), + }); + yield* harness.auth.controller.start(owner); + yield* Deferred.await(initializing); + const blamesNetwork = /reach(ed)? Google|network|ipv6/i; + + yield* TestClock.adjust("30 seconds"); + const stalled = yield* harness.auth.controller.subscribe(owner).pipe( + Stream.filter((state) => state.message !== "Starting Google sign-in."), + Stream.runHead, + Effect.map(Option.getOrThrow), + ); + assert.equal(stalled.phase, "starting"); + assert.notMatch(stalled.message ?? "", blamesNetwork); + assert.match(stalled.message ?? "", /not responded|stuck/i); + + yield* TestClock.adjust("270 seconds"); + const failed = yield* phase(harness.auth, "failed"); + assert.notMatch(failed.message ?? "", blamesNetwork); + assert.match(failed.message ?? "", /not respond|stuck/i); + }), + ); + it.effect("keeps the expired message once the Google link was shown", () => Effect.gen(function* () { const harness = yield* makeHarness(); @@ -383,7 +414,7 @@ it.layer(NodeServices.layer)("AntigravityAuth", (it) => { assert.isTrue(Exit.isFailure(competing)); const resumed = yield* phase(harness.auth, "waiting"); assert.equal(resumed.flowId, first.flowId); - assert.deepEqual(harness.events, ["process-open", "authenticate"]); + assert.deepEqual(harness.events, ["process-open", "initialize", "authenticate"]); yield* harness.auth.controller.cancel(owner, first.flowId!); }), ); From ef554ffd26ab755957b948f1d2fb765245284ce0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 2 Nov 2026 04:15:22 +0000 Subject: [PATCH 2/3] fix(server): Antigravity sign-in no longer blames the network when the agent never starts Sign-in said Antigravity had not reached Google, and later could not reach it, whenever the flow was still starting. On an Intel Mac the runtime hangs before it answers ACP initialize, so T3 blamed the network and IPv6 for a local process hang. runSignIn now awaits initialize before start and records it on the flow. Until the agent answers, the 30 second hint and the timeout say the runtime may be stuck on this machine; afterwards the network messages are unchanged. The real runtime caches initialize, and the test harness now does too. Fixes pingdotgg/t3code#29039 Co-Authored-By: Claude Opus 5.5 --- .../src/provider/AntigravityAuth.test.ts | 10 +++++---- apps/server/src/provider/AntigravityAuth.ts | 22 +++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/server/src/provider/AntigravityAuth.test.ts b/apps/server/src/provider/AntigravityAuth.test.ts index 60bc601135af8..efbbb30361777 100644 --- a/apps/server/src/provider/AntigravityAuth.test.ts +++ b/apps/server/src/provider/AntigravityAuth.test.ts @@ -82,19 +82,21 @@ const makeHarness = Effect.fn("makeAuthTestHarness")(function* ( yield* Deferred.succeed(closed, undefined); }), ); - const initialize = () => + // Cached like the real AcpSessionRuntime, so start() reuses an earlier initialize. + const initialize = yield* Effect.cached( Effect.gen(function* () { events.push("initialize"); yield* options.beforeInitialize ?? Effect.void; return options.supportsLogout === false ? { ...initialized, agentCapabilities: {} } : initialized; - }); + }), + ); return { - initialize, + initialize: () => initialize, start: () => Effect.gen(function* () { - yield* initialize(); + yield* initialize; events.push("authenticate"); yield* Deferred.succeed(authenticateCalled, undefined); if (options.interactive !== false && input.onAuthorizationUrl) { diff --git a/apps/server/src/provider/AntigravityAuth.ts b/apps/server/src/provider/AntigravityAuth.ts index 67f25681a1a4b..0cd116387e6a2 100644 --- a/apps/server/src/provider/AntigravityAuth.ts +++ b/apps/server/src/provider/AntigravityAuth.ts @@ -33,7 +33,12 @@ import type { ProviderAuthController } from "./ProviderAuthService.ts"; const AUTH_TIMEOUT_MS = 300_000; // A healthy agent shows the Google link or finishes within seconds. Its bundled Python has no // IPv4 fallback race, so a host with broken IPv6 waits about two minutes per Google request. +// An agent that never answers initialize is hung locally, so the network is not to blame. const NO_RESPONSE_HINT_MS = 30_000; +const NOT_STARTED_MESSAGE = + "Antigravity has not responded after 30 seconds. Its runtime may be stuck starting on this machine."; +const NOT_RESPONDING_MESSAGE = + "Antigravity did not respond. Its runtime may not run on this machine."; const NO_RESPONSE_MESSAGE = "Antigravity has not reached Google after 30 seconds. Check this machine's network connection. Broken IPv6 is a common cause."; const UNREACHABLE_MESSAGE = @@ -53,6 +58,7 @@ interface AuthFlow { readonly expiresAtMillis: number; state: ProviderAuthState; pending: AntigravityAuthorizationUrl | undefined; + initialized: boolean; callbackSent: boolean; fiber: Fiber.Fiber | undefined; forwarding: Fiber.Fiber | undefined; @@ -276,7 +282,10 @@ export const makeAntigravityAuth = Effect.fn("makeAntigravityAuth")(function* < lock.withPermits(1)( Effect.gen(function* () { if (activeFlow !== flow || flow.state.phase !== "starting") return; - yield* publishFlow(flow, { ...flow.state, message: NO_RESPONSE_MESSAGE }); + yield* publishFlow(flow, { + ...flow.state, + message: flow.initialized ? NO_RESPONSE_MESSAGE : NOT_STARTED_MESSAGE, + }); }), ), ), @@ -285,6 +294,8 @@ export const makeAntigravityAuth = Effect.fn("makeAntigravityAuth")(function* < const runtime = yield* options.makeRuntime({ onAuthorizationUrl: (url) => receiveAuthorizationUrl(flow, url), }); + yield* runtime.initialize(); + flow.initialized = true; const started = yield* runtime.start(); yield* lock.withPermits(1)( Effect.gen(function* () { @@ -309,9 +320,11 @@ export const makeAntigravityAuth = Effect.fn("makeAntigravityAuth")(function* < Effect.fail( setupError( "start", - flow.state.phase === "starting" - ? UNREACHABLE_MESSAGE - : "Google sign-in expired. Start sign-in again.", + flow.state.phase !== "starting" + ? "Google sign-in expired. Start sign-in again." + : flow.initialized + ? UNREACHABLE_MESSAGE + : NOT_RESPONDING_MESSAGE, ), ), }), @@ -398,6 +411,7 @@ export const makeAntigravityAuth = Effect.fn("makeAntigravityAuth")(function* < expiresAtMillis, state, pending: undefined, + initialized: false, callbackSent: false, fiber: undefined, forwarding: undefined, From c61dbe45c152f70aaf6e614961b4c471ab646bdc Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 03:45:35 +0000 Subject: [PATCH 3/3] chore(server): note why the Antigravity startup hint exists AI-assisted; verified with the tests and tools listed above. --- apps/server/src/provider/AntigravityAuth.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/server/src/provider/AntigravityAuth.ts b/apps/server/src/provider/AntigravityAuth.ts index 4320b186a673b..fcaf41275de12 100644 --- a/apps/server/src/provider/AntigravityAuth.ts +++ b/apps/server/src/provider/AntigravityAuth.ts @@ -31,10 +31,9 @@ import { import type { ProviderAuthController } from "./ProviderAuthService.ts"; const AUTH_TIMEOUT_MS = 300_000; -// Once initialized, a healthy agent shows the Google link or finishes within seconds. Its bundled -// Python has no IPv4 fallback race, so a host with broken IPv6 waits about two minutes per Google -// request. The hint is timed from initialize, not spawn: the one-file runtime unpacks itself on -// every launch, which can take about a minute on Windows before it answers at all. +// Once initialized, a healthy agent shows the Google link within seconds; its bundled Python has no +// IPv4 fallback, so broken IPv6 stalls each Google request. Startup is timed separately: the +// one-file runtime unpacks on every launch (about a minute on Windows) before it answers initialize. const NO_RESPONSE_HINT_MS = 30_000; const STARTUP_HINT_MS = 90_000; const NOT_STARTED_MESSAGE =