authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-04 04:25:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 04:25:32-07:00
logd487dfbf46be344e818725be70ee04be2436bfb4
treebb19764e9d0e25fe5a18ec85b129c798e7715b29
parenteb77683e5544e071db74831bae052bbd8a7d5f88
signature Signed by PGP key B5690EEEBB952194

fix(web): resume Antigravity threads without repeated sign-in (#9647)

Allow Antigravity threads to resume while saved Google sign-in is unchecked after a server restart. Keep confirmed authentication failures and installation errors visible. Validated with 136 focused tests, web typecheck, targeted lint, and CI. Browser verification was omitted at the maintainer's request. Created with GPT-6 Astra (preview) in Codex.

5 files changed, 76 insertions(+), 15 deletions(-)

apps/web/src/components/ChatView.logic.test.ts+11-4
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import {1import {
2 ANTIGRAVITY_DEFAULT_MODEL,
2 CheckpointRef,3 CheckpointRef,
3 EnvironmentId,4 EnvironmentId,
4 MessageId,5 MessageId,
...@@ -793,14 +794,20 @@ describe("resolveComposerProviderSelection", () => {...@@ -793,14 +794,20 @@ describe("resolveComposerProviderSelection", () => {
793 );794 );
794 });795 });
795796
796 it("blocks sends until Antigravity confirms authentication", () => {797 it("lets Antigravity check saved credentials when resuming after a restart", () => {
797 const provider = entry("antigravity", "google_work", {798 const provider = entry("antigravity", "google_work", {
799 status: "warning",
798 auth: { status: "unknown" },800 auth: { status: "unknown" },
799 models: catalogModels,801 models: [],
800 }).snapshot;802 }).snapshot;
801803
802 expect(getAntigravitySendBlockReason(provider, "gemini-pro")).toBe(804 expect(getAntigravitySendBlockReason(provider, "gemini-pro")).toBeNull();
803 "Sign in to Antigravity in provider settings before sending.",805 expect(getAntigravitySendBlockReason(provider, ANTIGRAVITY_DEFAULT_MODEL)).toBeNull();
806 expect(
807 getAntigravitySendBlockReason({ ...provider, models: catalogModels }, "gemini-pro"),
808 ).toBeNull();
809 expect(getAntigravitySendBlockReason(provider, "")).toBe(
810 "Choose an Antigravity model before sending.",
804 );811 );
805 });812 });
806813
apps/web/src/components/ChatView.logic.ts+6-3
...@@ -421,14 +421,17 @@ export function getAntigravitySendBlockReason(...@@ -421,14 +421,17 @@ export function getAntigravitySendBlockReason(
421 if (!provider.installed) {421 if (!provider.installed) {
422 return "Install Antigravity in provider settings before sending.";422 return "Install Antigravity in provider settings before sending.";
423 }423 }
424 if (provider.auth.status !== "authenticated") {424 if (provider.auth.status === "unauthenticated") {
425 return "Sign in to Antigravity in provider settings before sending.";425 return "Sign in to Antigravity in provider settings before sending.";
426 }426 }
427 const slug = model.trim();
428 if (slug.length === 0) return "Choose an Antigravity model before sending.";
429 // A restart clears the account status and catalog. Session startup checks
430 // saved credentials and validates the model before sending the prompt.
431 if (provider.auth.status === "unknown") return null;
427 if (provider.models.length === 0) {432 if (provider.models.length === 0) {
428 return "Refresh Antigravity models in provider settings before sending.";433 return "Refresh Antigravity models in provider settings before sending.";
429 }434 }
430 const slug = model.trim();
431 if (slug.length === 0) return "Choose an Antigravity model before sending.";
432 // A saved model that left the catalog is kept in the picker as unavailable435 // A saved model that left the catalog is kept in the picker as unavailable
433 // so the user sees what the thread used. The server rejects it at turn436 // so the user sees what the thread used. The server rejects it at turn
434 // start, so block here unless the provider is in an error state, where a437 // start, so block here unless the provider is in an error state, where a
apps/web/src/components/chat/ProviderStatusBanner.test.tsx+36
...@@ -28,6 +28,42 @@ function warningProvider(): ServerProvider {...@@ -28,6 +28,42 @@ function warningProvider(): ServerProvider {
28}28}
2929
30describe("ProviderStatusBanner", () => {30describe("ProviderStatusBanner", () => {
31 it("waits for an Antigravity auth result before showing a sign-in warning", () => {
32 const status: ServerProvider = {
33 ...warningProvider(),
34 instanceId: ProviderInstanceId.make("google_work"),
35 driver: ProviderDriverKind.make("antigravity"),
36 auth: { status: "unknown" },
37 message: "Antigravity is installed. Google account access is not checked yet.",
38 };
39
40 expect(shouldShowProviderStatusBanner(status, null)).toBe(false);
41 expect(
42 shouldShowProviderStatusBanner(
43 {
44 ...status,
45 auth: { status: "unauthenticated" },
46 message: "Sign in with Google to use Antigravity.",
47 },
48 null,
49 ),
50 ).toBe(true);
51 });
52
53 it("shows Antigravity installation and startup failures before auth is checked", () => {
54 const status: ServerProvider = {
55 ...warningProvider(),
56 driver: ProviderDriverKind.make("antigravity"),
57 auth: { status: "unknown" },
58 };
59
60 expect(shouldShowProviderStatusBanner({ ...status, installed: false }, null)).toBe(true);
61 expect(shouldShowProviderStatusBanner({ ...status, status: "error" }, null)).toBe(true);
62 expect(
63 shouldShowProviderStatusBanner({ ...status, driver: ProviderDriverKind.make("codex") }, null),
64 ).toBe(true);
65 });
66
31 it("stays hidden after its current warning is dismissed", () => {67 it("stays hidden after its current warning is dismissed", () => {
32 const status = warningProvider();68 const status = warningProvider();
3369
apps/web/src/components/chat/ProviderStatusBanner.tsx+15-4
...@@ -7,9 +7,20 @@ import { formatProviderDriverKindLabel } from "../../providerModels";...@@ -7,9 +7,20 @@ import { formatProviderDriverKindLabel } from "../../providerModels";
7import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";7import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
88
9export function getProviderStatusBannerKey(status: ServerProvider | null): string | null {9export function getProviderStatusBannerKey(status: ServerProvider | null): string | null {
10 return !status || status.status === "ready" || status.status === "disabled"10 if (!status || status.status === "ready" || status.status === "disabled") return null;
11 ? null11 // Antigravity checks saved credentials when a session starts. Its local
12 : [status.instanceId, status.status, status.auth.status, status.message ?? ""].join("\u0000");12 // health check leaves auth unknown after a restart, which is not a failure.
13 if (
14 status.driver === "antigravity" &&
15 status.installed &&
16 status.status === "warning" &&
17 status.auth.status === "unknown"
18 ) {
19 return null;
20 }
21 return [status.instanceId, status.status, status.auth.status, status.message ?? ""].join(
22 "\u0000",
23 );
13}24}
1425
15export function shouldShowProviderStatusBanner(26export function shouldShowProviderStatusBanner(
...@@ -59,7 +70,7 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({...@@ -59,7 +70,7 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({
59 onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void;70 onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void;
60 status: ServerProvider | null;71 status: ServerProvider | null;
61}) {72}) {
62 if (!status || status.status === "ready" || status.status === "disabled") {73 if (!status || getProviderStatusBannerKey(status) === null) {
63 return null;74 return null;
64 }75 }
6576
docs/user/providers-antigravity.md+8-4
...@@ -179,10 +179,14 @@ paid-plan tier or remaining subscription quota. See Google's [Antigravity plans]...@@ -179,10 +179,14 @@ paid-plan tier or remaining subscription quota. See Google's [Antigravity plans]
179[personal Google sign-in guide][google-setup].179[personal Google sign-in guide][google-setup].
180180
181After an environment restarts, Google sign-in can show as not checked until an authenticated181After an environment restarts, Google sign-in can show as not checked until an authenticated
182session succeeds. To check account access and reload models, use **Refresh provider status**182session succeeds. You can continue an existing thread. Antigravity checks saved Google sign-in
183in web or desktop provider settings, or **Refresh models** in the mobile model picker. Refresh183when the session starts. An unchecked status does not require signing in again.
184uses saved Google sign-in and does not open a login page. If sign-in is required, use the184
185provider's setup controls. Automatic status checks verify the installation only.185To check account access and reload models on web or desktop, open **Settings** > **Providers**
186and select the circular arrow beside **Checked** at the top of the page. Its tooltip says
187**Refresh provider status**. On mobile, use **Refresh models** in the model picker.
188Refresh uses saved Google sign-in and does not open a login page. If sign-in is required,
189use the provider's setup controls. Automatic status checks verify the installation only.
186190
187The packaged runtime can be slow to start, especially on Windows. Health checks, model refresh,191The packaged runtime can be slow to start, especially on Windows. Health checks, model refresh,
188and sign-out each allow up to 90 seconds before reporting a timeout.192and sign-out each allow up to 90 seconds before reporting a timeout.