authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-15 19:02:12-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-15 19:02:12-07:00
log8c18b5bb21a5349fbadba8c77456ea34c41b7339
treef8bae6ddfd73c9dc9eb2f707d6b528d950aac567
parent935c55b3778fdeae0e25b250ce2a9fa7e79c0327
signature Signed by PGP key B5690EEEBB952194

fix(antigravity): stop health checks from filling the disk with _MEI folders (#12008)

The health probe launched the PyInstaller ACP binary every minute and force killed it, leaving about 1 GB of _MEI files per run. The probe now resolves the install on disk without spawning. Each ACP process gets its own temp directory under the profile that is removed when the runtime closes, and the driver sweeps the profile temp root on create. Continues #11657 by Vita Skacel. Owned temp directory approach from #9626 by ariszz. Co-authored-by: Vita Skacel <skacel.vita@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

5 files changed, 244 insertions(+), 20 deletions(-)

apps/server/src/provider/Drivers/AntigravityDriver.test.ts+84-2
...@@ -31,6 +31,7 @@ import {...@@ -31,6 +31,7 @@ import {
31import {31import {
32 ANTIGRAVITY_AUTH_STDOUT_PREFIX,32 ANTIGRAVITY_AUTH_STDOUT_PREFIX,
33 resolveAntigravityProfileDirectory,33 resolveAntigravityProfileDirectory,
34 resolveAntigravityRuntimeTempDirectory,
34} from "../antigravityAuthSupport.ts";35} from "../antigravityAuthSupport.ts";
35import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";36import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
36import * as ModelManifest from "../ModelManifest.ts";37import * as ModelManifest from "../ModelManifest.ts";
...@@ -58,7 +59,7 @@ function shellQuote(value: string): string {...@@ -58,7 +59,7 @@ function shellQuote(value: string): string {
58}59}
5960
60const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (61const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
61 options: { readonly config?: Partial<AntigravitySettings> } = {},62 options: { readonly config?: Partial<AntigravitySettings>; readonly enabled?: boolean } = {},
62) {63) {
63 const fs = yield* FileSystem.FileSystem;64 const fs = yield* FileSystem.FileSystem;
64 const path = yield* Path.Path;65 const path = yield* Path.Path;
...@@ -124,11 +125,22 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (...@@ -124,11 +125,22 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
124 forceFileStorage: string | undefined;125 forceFileStorage: string | undefined;
125 credentialKeys: ReadonlyArray<string>;126 credentialKeys: ReadonlyArray<string>;
126 geminiApiKey: string | undefined;127 geminiApiKey: string | undefined;
128 tempDirectory: string | undefined;
127 handle: ChildProcessSpawner.ChildProcessHandle;129 handle: ChildProcessSpawner.ChildProcessHandle;
128 }> = [];130 }> = [];
129131
130 const installation = Layer.mock(AntigravityInstallation)({132 const installation = Layer.mock(AntigravityInstallation)({
131 managedDirectory: root,133 managedDirectory: root,
134 resolve: () =>
135 Effect.gen(function* () {
136 if (controls.failResolution) {
137 return yield* new AntigravityInstallationError({
138 operation: "resolve",
139 detail: "Fixture resolution failed.",
140 });
141 }
142 return controls.selected;
143 }),
132 acquire: (binaryPath, environment) =>144 acquire: (binaryPath, environment) =>
133 Effect.gen(function* () {145 Effect.gen(function* () {
134 acquisitions.push({ binaryPath, path: environment?.PATH });146 acquisitions.push({ binaryPath, path: environment?.PATH });
...@@ -166,6 +178,10 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (...@@ -166,6 +178,10 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
166 blockedCredentialKeys.has(key.toUpperCase()),178 blockedCredentialKeys.has(key.toUpperCase()),
167 ),179 ),
168 geminiApiKey: environment.GEMINI_API_KEY,180 geminiApiKey: environment.GEMINI_API_KEY,
181 // Only the agent gets a per-process temp directory. Other launches
182 // inherit the host TMPDIR.
183 tempDirectory:
184 environment.ANTIGRAVITY_HARNESS_PATH === undefined ? undefined : environment.TMPDIR,
169 handle,185 handle,
170 });186 });
171 return handle;187 return handle;
...@@ -174,7 +190,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (...@@ -174,7 +190,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
174 const instance = yield* AntigravityDriver.create({190 const instance = yield* AntigravityDriver.create({
175 instanceId,191 instanceId,
176 displayName: "Google test account",192 displayName: "Google test account",
177 enabled: false,193 enabled: options.enabled ?? false,
178 config: { ...AntigravityDriver.defaultConfig(), ...options.config },194 config: { ...AntigravityDriver.defaultConfig(), ...options.config },
179 environment: [195 environment: [
180 { name: "PATH", value: instancePath },196 { name: "PATH", value: instancePath },
...@@ -207,12 +223,14 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (...@@ -207,12 +223,14 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
207 yield* launch.handle.exitCode.pipe(Effect.ignore);223 yield* launch.handle.exitCode.pipe(Effect.ignore);
208 expect(yield* launch.handle.isRunning).toBe(false);224 expect(yield* launch.handle.isRunning).toBe(false);
209 if (launch.cwd) expect(yield* fs.exists(launch.cwd)).toBe(false);225 if (launch.cwd) expect(yield* fs.exists(launch.cwd)).toBe(false);
226 if (launch.tempDirectory) expect(yield* fs.exists(launch.tempDirectory)).toBe(false);
210 }227 }
211 });228 });
212 return {229 return {
213 instance,230 instance,
214 refresh,231 refresh,
215 fs,232 fs,
233 path,
216 profileDirectory,234 profileDirectory,
217 instancePath,235 instancePath,
218 first,236 first,
...@@ -435,4 +453,68 @@ it.layer(testLayer)("AntigravityDriver", (it) => {...@@ -435,4 +453,68 @@ it.layer(testLayer)("AntigravityDriver", (it) => {
435 yield* h.assertClosed;453 yield* h.assertClosed;
436 }).pipe(Effect.scoped),454 }).pipe(Effect.scoped),
437 );455 );
456
457 it.effect.skipIf(windowsHost)(
458 "gives each process its own temp directory and removes it when the process closes",
459 () =>
460 Effect.gen(function* () {
461 const h = yield* makeHarness();
462 const tempRoot = resolveAntigravityRuntimeTempDirectory(h.profileDirectory);
463 yield* h.refresh();
464 yield* h.refresh();
465 const directories = h.launches.flatMap((launch) =>
466 launch.tempDirectory === undefined ? [] : [launch.tempDirectory],
467 );
468 expect(directories).toHaveLength(2);
469 for (const directory of directories) {
470 expect(h.path.dirname(directory)).toBe(tempRoot);
471 }
472 expect(new Set(directories).size).toBe(2);
473 yield* h.assertClosed;
474 }).pipe(Effect.scoped),
475 );
476
477 it.effect.skipIf(windowsHost)(
478 "removes runtime temp directories left by a previous server on create",
479 () =>
480 Effect.gen(function* () {
481 const fs = yield* FileSystem.FileSystem;
482 const path = yield* Path.Path;
483 const config = yield* ServerConfig;
484 const instanceId = ProviderInstanceId.make("antigravity-orphan-sweep");
485 const tempRoot = resolveAntigravityRuntimeTempDirectory(
486 resolveAntigravityProfileDirectory(config.stateDir, instanceId),
487 );
488 const orphan = path.join(tempRoot, "run-orphan", "_MEI123", "google3");
489 yield* fs.makeDirectory(orphan, { recursive: true });
490 yield* fs.writeFileString(path.join(orphan, "payload.bin"), "stale");
491 yield* AntigravityDriver.create({
492 instanceId,
493 displayName: "Sweep",
494 enabled: false,
495 config: AntigravityDriver.defaultConfig(),
496 environment: [],
497 }).pipe(
498 Effect.provide(
499 Layer.mock(AntigravityInstallation)({
500 managedDirectory: config.stateDir,
501 resolve: () => Effect.die("unused"),
502 acquire: () => Effect.die("unused"),
503 }),
504 ),
505 );
506 expect(yield* fs.exists(tempRoot)).toBe(false);
507 }).pipe(Effect.scoped),
508 );
509
510 it.effect("probes through installation resolution without launching a process", () =>
511 Effect.gen(function* () {
512 const h = yield* makeHarness({ enabled: true });
513 const snapshot = yield* h.instance.snapshot.refresh;
514 expect(snapshot.installed).toBe(true);
515 expect(snapshot.version).toBe(h.first.version);
516 expect(h.launches).toEqual([]);
517 expect(h.acquisitions).toEqual([]);
518 }).pipe(Effect.scoped),
519 );
438});520});
apps/server/src/provider/Drivers/AntigravityDriver.ts+78-17
...@@ -30,6 +30,7 @@ import {...@@ -30,6 +30,7 @@ import {
30 isAntigravitySignInRequiredError,30 isAntigravitySignInRequiredError,
31 prepareAntigravityProfile,31 prepareAntigravityProfile,
32 resolveAntigravityProfileDirectory,32 resolveAntigravityProfileDirectory,
33 resolveAntigravityRuntimeTempDirectory,
33 type AntigravityAuthConfig,34 type AntigravityAuthConfig,
34} from "../antigravityAuthSupport.ts";35} from "../antigravityAuthSupport.ts";
35import {36import {
...@@ -38,7 +39,10 @@ import {...@@ -38,7 +39,10 @@ import {
38} from "../acp/AntigravityAcpSupport.ts";39} from "../acp/AntigravityAcpSupport.ts";
39import type { AcpSessionRuntime, AcpSessionRuntimeStartResult } from "../acp/AcpSessionRuntime.ts";40import type { AcpSessionRuntime, AcpSessionRuntimeStartResult } from "../acp/AcpSessionRuntime.ts";
40import type { ServerProviderDraft } from "../providerSnapshot.ts";41import type { ServerProviderDraft } from "../providerSnapshot.ts";
41import { removeAntigravitySessionFiles } from "../acp/AntigravitySessionFiles.ts";42import {
43 removeAntigravityRuntimeTempDirs,
44 removeAntigravitySessionFiles,
45} from "../acp/AntigravitySessionFiles.ts";
42import { ProviderDriverError } from "../Errors.ts";46import { ProviderDriverError } from "../Errors.ts";
43import { makeAntigravityAdapter } from "../Layers/AntigravityAdapter.ts";47import { makeAntigravityAdapter } from "../Layers/AntigravityAdapter.ts";
44import { makeAntigravityProvider } from "../Layers/AntigravityProvider.ts";48import { makeAntigravityProvider } from "../Layers/AntigravityProvider.ts";
...@@ -98,6 +102,11 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD...@@ -98,6 +102,11 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
98 serverConfig.stateDir,102 serverConfig.stateDir,
99 instanceId,103 instanceId,
100 );104 );
105 // No process of this instance exists yet, so every runtime temp
106 // directory left under the profile is an orphan from a killed server.
107 yield* removeAntigravityRuntimeTempDirs(
108 resolveAntigravityRuntimeTempDirectory(profileDirectory),
109 ).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem));
101 const continuationIdentity = defaultProviderContinuationIdentity({110 const continuationIdentity = defaultProviderContinuationIdentity({
102 driverKind: DRIVER,111 driverKind: DRIVER,
103 instanceId,112 instanceId,
...@@ -142,6 +151,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD...@@ -142,6 +151,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
142 instanceId,151 instanceId,
143 operation: "resolve",152 operation: "resolve",
144 detail: cause.detail,153 detail: cause.detail,
154 cause,
145 }),155 }),
146 ),156 ),
147 );157 );
...@@ -155,6 +165,32 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD...@@ -155,6 +165,32 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
155 Effect.provideService(Path.Path, path),165 Effect.provideService(Path.Path, path),
156 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),166 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
157 );167 );
168 // Each process unpacks into its own directory that dies with the
169 // runtime scope, after the child is killed. A shared directory would
170 // let one session's teardown delete files a sibling still reads.
171 // Removal is best effort: a handle can outlive the kill on Windows,
172 // and the sweep on the next driver start reclaims what is left.
173 const runtimeTempDirectory = yield* Effect.acquireRelease(
174 fileSystem.makeTempDirectory({ directory: profile.tempDirectory, prefix: "run-" }).pipe(
175 Effect.mapError(
176 (cause) =>
177 new ProviderSetupError({
178 instanceId,
179 operation: "start",
180 detail: "Could not create an Antigravity runtime temp directory.",
181 cause,
182 }),
183 ),
184 ),
185 (directory) =>
186 fileSystem
187 .remove(directory, { recursive: true, force: true })
188 .pipe(
189 Effect.catch(() =>
190 Effect.logWarning("Could not remove an Antigravity runtime temp directory."),
191 ),
192 ),
193 );
158 const runtime = yield* makeAntigravityAcpRuntime({194 const runtime = yield* makeAntigravityAcpRuntime({
159 ...input,195 ...input,
160 authMethod: auth.authMethod,196 authMethod: auth.authMethod,
...@@ -165,6 +201,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD...@@ -165,6 +201,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
165 cwd: input.cwd,201 cwd: input.cwd,
166 baseEnv: withAgentDeviceEnvironment(processEnvironment, input),202 baseEnv: withAgentDeviceEnvironment(processEnvironment, input),
167 auth,203 auth,
204 runtimeTempDirectory,
168 }),205 }),
169 }).pipe(Effect.provideService(Crypto.Crypto, crypto));206 }).pipe(Effect.provideService(Crypto.Crypto, crypto));
170 return {207 return {
...@@ -258,24 +295,48 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD...@@ -258,24 +295,48 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
258 // Kick the TTL-gated manifest refresh alongside the health check, as295 // Kick the TTL-gated manifest refresh alongside the health check, as
259 // Codex and Claude do. Without it an environment that only runs296 // Codex and Claude do. Without it an environment that only runs
260 // Antigravity would keep classifying against a stale disk cache.297 // Antigravity would keep classifying against a stale disk cache.
298 // The probe must not spawn. The agent is a PyInstaller one-file bundle
299 // that unpacks about 1 GB per launch, and the health check runs every
300 // minute. Resolving the install on disk is enough to report installed
301 // and version. The response below is synthetic: only agentInfo.version
302 // is read from it. Sessions and manual refreshes still spawn.
261 const probe = Effect.gen(function* () {303 const probe = Effect.gen(function* () {
262 yield* modelManifest.refreshInBackground;304 yield* modelManifest.refreshInBackground;
263 const processScope = yield* Scope.make();305 if (authConfigIssue !== null) {
264 yield* Effect.addFinalizer((exit) => Scope.close(processScope, exit));306 return yield* new ProviderSetupError({
265 return yield* authFlow307 instanceId,
266 .withProcess(308 operation: "configure",
267 Scope.close(processScope, Exit.void),309 detail: authConfigIssue,
268 Effect.gen(function* () {310 });
269 const runtime = yield* makeRuntime({311 }
270 cwd: serverConfig.stateDir,312 const executable = yield* installation
271 clientInfo: { name: "t3-code-provider-probe", version: "0.0.0" },313 .resolve(settings.binaryPath, processEnvironment)
272 mcpServers: [],314 .pipe(
273 });315 Effect.mapError(
274 return yield* runtime.initialize();316 (cause) =>
275 }),317 new ProviderSetupError({
276 )318 instanceId,
277 .pipe(Effect.provideService(Scope.Scope, processScope));319 operation: "resolve",
278 }).pipe(Effect.scoped);320 detail: cause.detail,
321 cause,
322 }),
323 ),
324 );
325 return {
326 protocolVersion: 1,
327 agentCapabilities: {
328 loadSession: true,
329 promptCapabilities: { image: true, audio: true, embeddedContext: true },
330 sessionCapabilities: { list: {}, resume: {} },
331 },
332 authMethods: [{ id: "oauth-personal", name: "Log in with Google" }],
333 agentInfo: {
334 name: "antigravity-acp",
335 title: "Google Antigravity",
336 version: executable.version ?? "unknown",
337 },
338 };
339 });
279340
280 const provider = yield* makeAntigravityProvider(settings, {341 const provider = yield* makeAntigravityProvider(settings, {
281 stampIdentity: classifyModels,342 stampIdentity: classifyModels,
apps/server/src/provider/acp/AntigravitySessionFiles.ts+18
...@@ -41,3 +41,21 @@ export const removeAntigravitySessionFiles = Effect.fn("removeAntigravitySession...@@ -41,3 +41,21 @@ export const removeAntigravitySessionFiles = Effect.fn("removeAntigravitySession
41 },41 },
42 Effect.catch(() => Effect.logWarning("Could not remove temporary Antigravity session files.")),42 Effect.catch(() => Effect.logWarning("Could not remove temporary Antigravity session files.")),
43);43);
44
45/**
46 * Removes every per-process runtime temp directory under the profile. Call
47 * once when the driver starts, before it launches any process, so a previous
48 * server that was killed mid-session cannot leave unpacked runtimes behind.
49 * Only the profile-owned directory is touched. The system temp directory
50 * belongs to other programs and Windows does not lock data files, so sweeping
51 * it could gut a live extraction.
52 */
53export const removeAntigravityRuntimeTempDirs = Effect.fn("removeAntigravityRuntimeTempDirs")(
54 function* (tempDirectory: string) {
55 const fs = yield* FileSystem.FileSystem;
56 yield* fs.remove(tempDirectory, { recursive: true, force: true });
57 },
58 Effect.catch(() =>
59 Effect.logWarning("Could not remove leftover Antigravity runtime temp files."),
60 ),
61);
apps/server/src/provider/antigravityAuthSupport.test.ts+43
...@@ -51,6 +51,7 @@ describe("Antigravity process environment", () => {...@@ -51,6 +51,7 @@ describe("Antigravity process environment", () => {
51 geminiHome: "/t3/userdata/providers/antigravity/profile",51 geminiHome: "/t3/userdata/providers/antigravity/profile",
52 acpDirectory: "/t3/userdata/providers/antigravity/profile/antigravity-acp",52 acpDirectory: "/t3/userdata/providers/antigravity/profile/antigravity-acp",
53 tokenPath: "/t3/userdata/providers/antigravity/profile/antigravity-acp/acp_token.json",53 tokenPath: "/t3/userdata/providers/antigravity/profile/antigravity-acp/acp_token.json",
54 tempDirectory: "/t3/userdata/providers/antigravity/profile/antigravity-acp/tmp",
54 browserCommand: "managed-browser-helper",55 browserCommand: "managed-browser-helper",
55 };56 };
5657
...@@ -101,6 +102,7 @@ describe("Antigravity process environment", () => {...@@ -101,6 +102,7 @@ describe("Antigravity process environment", () => {
101 BROWSER: profile.browserCommand,102 BROWSER: profile.browserCommand,
102 PYTHONUNBUFFERED: "1",103 PYTHONUNBUFFERED: "1",
103 ELECTRON_RUN_AS_NODE: "1",104 ELECTRON_RUN_AS_NODE: "1",
105 TMPDIR: profile.tempDirectory,
104 },106 },
105 });107 });
106 });108 });
...@@ -187,6 +189,47 @@ describe("Antigravity process environment", () => {...@@ -187,6 +189,47 @@ describe("Antigravity process environment", () => {
187 ).toBeNull();189 ).toBeNull();
188 });190 });
189191
192 it("isolates TEMP and TMP to the profile directory on Windows", () => {
193 const windowsProfile: AntigravityProfile = {
194 platform: "win32",
195 geminiHome: "C:\\state\\providers\\antigravity\\profile",
196 acpDirectory: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp",
197 tokenPath: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp\\acp_token.json",
198 tempDirectory: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp\\tmp",
199 browserCommand: "managed-browser-helper",
200 };
201 const input = {
202 installation: {
203 executablePath: "C:\\release\\agy_acp_server.exe",
204 harnessPath: "C:\\release\\localharness_external.exe",
205 },
206 profile: windowsProfile,
207 cwd: "C:\\project",
208 baseEnv: { PATH: "C:\\Windows\\system32", TEMP: "C:\\Users\\user\\AppData\\Local\\Temp" },
209 };
210 const shared = buildAntigravityAcpSpawnInput(input);
211 expect(shared.env?.TEMP).toBe(windowsProfile.tempDirectory);
212 expect(shared.env?.TMP).toBe(windowsProfile.tempDirectory);
213 const perRun = buildAntigravityAcpSpawnInput({
214 ...input,
215 runtimeTempDirectory: `${windowsProfile.tempDirectory}\\run-1`,
216 });
217 expect(perRun.env?.TEMP).toBe(`${windowsProfile.tempDirectory}\\run-1`);
218 expect(perRun.env?.TMP).toBe(`${windowsProfile.tempDirectory}\\run-1`);
219 });
220
221 it("isolates TMPDIR to the profile directory on POSIX hosts", () => {
222 const spawn = buildAntigravityAcpSpawnInput({
223 installation: { executablePath: "/release/acp", harnessPath: "/release/harness" },
224 profile,
225 cwd: "/project",
226 baseEnv: { TMPDIR: "/tmp" },
227 runtimeTempDirectory: `${profile.tempDirectory}/run-1`,
228 });
229 expect(spawn.env?.TMPDIR).toBe(`${profile.tempDirectory}/run-1`);
230 expect(spawn.env?.TEMP).toBeUndefined();
231 });
232
190 it("uses the registry launch arguments for each supported host platform", () => {233 it("uses the registry launch arguments for each supported host platform", () => {
191 for (const platform of ["linux", "darwin", "win32"] as const) {234 for (const platform of ["linux", "darwin", "win32"] as const) {
192 const spawn = buildAntigravityAcpSpawnInput({235 const spawn = buildAntigravityAcpSpawnInput({
apps/server/src/provider/antigravityAuthSupport.ts+21-1
...@@ -85,6 +85,8 @@ export interface AntigravityProfile {...@@ -85,6 +85,8 @@ export interface AntigravityProfile {
85 readonly geminiHome: string;85 readonly geminiHome: string;
86 readonly acpDirectory: string;86 readonly acpDirectory: string;
87 readonly tokenPath: string;87 readonly tokenPath: string;
88 /** Parent of the per-process temp directories PyInstaller unpacks into. */
89 readonly tempDirectory: string;
88 readonly browserCommand: string;90 readonly browserCommand: string;
89}91}
9092
...@@ -191,6 +193,11 @@ export function resolveAntigravityProfileDirectory(...@@ -191,6 +193,11 @@ export function resolveAntigravityProfileDirectory(
191 return NodePath.join(stateDir, "providers", "antigravity", directoryName);193 return NodePath.join(stateDir, "providers", "antigravity", directoryName);
192}194}
193195
196/** Parent of the per-process runtime temp directories inside a profile. */
197export function resolveAntigravityRuntimeTempDirectory(profileDirectory: string): string {
198 return NodePath.join(profileDirectory, "antigravity-acp", "tmp");
199}
200
194function quoteBrowserArgument(value: string): string {201function quoteBrowserArgument(value: string): string {
195 return `'${value.replaceAll("'", `'"'"'`)}'`;202 return `'${value.replaceAll("'", `'"'"'`)}'`;
196}203}
...@@ -199,6 +206,7 @@ function antigravityEnvironment(...@@ -199,6 +206,7 @@ function antigravityEnvironment(
199 profile: AntigravityProfile,206 profile: AntigravityProfile,
200 baseEnv: NodeJS.ProcessEnv,207 baseEnv: NodeJS.ProcessEnv,
201 auth: AntigravityAuthConfig,208 auth: AntigravityAuthConfig,
209 runtimeTempDirectory?: string,
202) {210) {
203 const environment: NodeJS.ProcessEnv = {};211 const environment: NodeJS.ProcessEnv = {};
204 for (const [key, value] of Object.entries(baseEnv)) {212 for (const [key, value] of Object.entries(baseEnv)) {
...@@ -214,6 +222,10 @@ function antigravityEnvironment(...@@ -214,6 +222,10 @@ function antigravityEnvironment(
214 : auth.authMethod === "agent-platform" && auth.apiKey222 : auth.authMethod === "agent-platform" && auth.apiKey
215 ? { GOOGLE_API_KEY: auth.apiKey }223 ? { GOOGLE_API_KEY: auth.apiKey }
216 : {};224 : {};
225 // The agent is a PyInstaller one-file bundle. It unpacks about 1 GB into
226 // the system temp directory per launch and a force kill leaves that behind.
227 // Point it at a T3-owned directory so the driver can reclaim the space.
228 const tempDirectory = runtimeTempDirectory ?? profile.tempDirectory;
217 return {229 return {
218 ...environment,230 ...environment,
219 ...credential,231 ...credential,
...@@ -222,6 +234,9 @@ function antigravityEnvironment(...@@ -222,6 +234,9 @@ function antigravityEnvironment(
222 BROWSER: profile.browserCommand,234 BROWSER: profile.browserCommand,
223 PYTHONUNBUFFERED: "1",235 PYTHONUNBUFFERED: "1",
224 ELECTRON_RUN_AS_NODE: "1",236 ELECTRON_RUN_AS_NODE: "1",
237 ...(profile.platform === "win32"
238 ? { TEMP: tempDirectory, TMP: tempDirectory }
239 : { TMPDIR: tempDirectory }),
225 };240 };
226}241}
227242
...@@ -311,11 +326,13 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")(...@@ -311,11 +326,13 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")(
311326
312 const geminiHome = path.resolve(input.profileDirectory);327 const geminiHome = path.resolve(input.profileDirectory);
313 const acpDirectory = path.join(geminiHome, "antigravity-acp");328 const acpDirectory = path.join(geminiHome, "antigravity-acp");
329 const tempDirectory = resolveAntigravityRuntimeTempDirectory(geminiHome);
314 const profile: AntigravityProfile = {330 const profile: AntigravityProfile = {
315 platform,331 platform,
316 geminiHome,332 geminiHome,
317 acpDirectory,333 acpDirectory,
318 tokenPath: path.join(acpDirectory, "acp_token.json"),334 tokenPath: path.join(acpDirectory, "acp_token.json"),
335 tempDirectory,
319 browserCommand,336 browserCommand,
320 };337 };
321 const environment = antigravityEnvironment(profile, input.baseEnv ?? process.env, auth);338 const environment = antigravityEnvironment(profile, input.baseEnv ?? process.env, auth);
...@@ -358,7 +375,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")(...@@ -358,7 +375,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")(
358 ),375 ),
359 );376 );
360377
361 for (const directory of [geminiHome, acpDirectory]) {378 for (const directory of [geminiHome, acpDirectory, tempDirectory]) {
362 yield* fs379 yield* fs
363 .makeDirectory(directory, { recursive: true, mode: 0o700 })380 .makeDirectory(directory, { recursive: true, mode: 0o700 })
364 .pipe(381 .pipe(
...@@ -400,6 +417,8 @@ export function buildAntigravityAcpSpawnInput(input: {...@@ -400,6 +417,8 @@ export function buildAntigravityAcpSpawnInput(input: {
400 readonly cwd: string;417 readonly cwd: string;
401 readonly baseEnv?: NodeJS.ProcessEnv;418 readonly baseEnv?: NodeJS.ProcessEnv;
402 readonly auth?: AntigravityAuthConfig;419 readonly auth?: AntigravityAuthConfig;
420 /** Per-process temp directory. Defaults to the profile's shared temp directory. */
421 readonly runtimeTempDirectory?: string;
403}): AcpSpawnInput {422}): AcpSpawnInput {
404 return {423 return {
405 command: input.installation.executablePath,424 command: input.installation.executablePath,
...@@ -410,6 +429,7 @@ export function buildAntigravityAcpSpawnInput(input: {...@@ -410,6 +429,7 @@ export function buildAntigravityAcpSpawnInput(input: {
410 input.profile,429 input.profile,
411 input.baseEnv ?? process.env,430 input.baseEnv ?? process.env,
412 input.auth ?? ANTIGRAVITY_PERSONAL_AUTH,431 input.auth ?? ANTIGRAVITY_PERSONAL_AUTH,
432 input.runtimeTempDirectory,
413 ),433 ),
414 ANTIGRAVITY_HARNESS_PATH: input.installation.harnessPath,434 ANTIGRAVITY_HARNESS_PATH: input.installation.harnessPath,
415 },435 },