diff --git a/apps/server/src/terminal/Manager.test.ts b/apps/server/src/terminal/Manager.test.ts index d89316cc74951..6fbcd4ffba84b 100644 --- a/apps/server/src/terminal/Manager.test.ts +++ b/apps/server/src/terminal/Manager.test.ts @@ -1944,6 +1944,20 @@ it.layer( }), ); + it.effect("expands a provider home named in any case on Windows", () => + Effect.gen(function* () { + const path = yield* Path.Path; + const home = yield* HostProcess.HomeDirectory; + const { manager, ptyAdapter } = yield* createManager(5, { + env: { GROK_HOME: "C:\\inherited-grok" }, + }); + + yield* manager.open(openInput({ env: { grok_home: "~/.grok-work" } })); + + expect(ptyAdapter.spawnInputs[0]?.env.GROK_HOME).toBe(path.join(home, ".grok-work")); + }).pipe(Effect.provide(layerWithHostPlatform("win32"))), + ); + it.effect("fails closed when a provider instance is missing", () => Effect.gen(function* () { const providerInstanceId = ProviderInstanceId.make("deleted_instance"); diff --git a/apps/server/src/terminal/Manager.ts b/apps/server/src/terminal/Manager.ts index 3ee43635f45c5..839d7ad27e7b0 100644 --- a/apps/server/src/terminal/Manager.ts +++ b/apps/server/src/terminal/Manager.ts @@ -69,7 +69,7 @@ import { makeClaudeEnvironment } from "../provider/Drivers/ClaudeHome.ts"; import { deriveProviderInstanceConfigMap } from "../provider/ProviderInstanceRegistryHydration.ts"; import * as ServerSettings from "../serverSettings.ts"; import { increment, terminalSessionsTotal } from "../observability/Metrics.ts"; -import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion"; +import { expandHomeVariable } from "@t3tools/provider-core/server/pathExpansion"; import * as ProcessRunner from "../processRunner.ts"; import * as PortScanner from "../preview/PortScanner.ts"; import * as NativeTelemetryClient from "../resourceTelemetry/NativeTelemetryClient.ts"; @@ -1367,8 +1367,11 @@ function createTerminalSpawnEnv( platform === "win32" ? Object.keys(spawnEnv).find((candidate) => candidate.toLowerCase() === key.toLowerCase()) : undefined; - spawnEnv[existingKey ?? key] = - key === "CODEX_HOME" || key === "CLAUDE_CONFIG_DIR" ? expandHomePath(value, home) : value; + spawnEnv[existingKey ?? key] = expandHomeVariable( + platform === "win32" ? key.toUpperCase() : key, + value, + home, + ); } } if (!spawnEnv.COLORTERM && runtimeEnv?.COLORTERM === undefined) { diff --git a/packages/provider-core/src/server/instanceEnvironment.test.ts b/packages/provider-core/src/server/instanceEnvironment.test.ts index 114905d3f2115..374559f7f099a 100644 --- a/packages/provider-core/src/server/instanceEnvironment.test.ts +++ b/packages/provider-core/src/server/instanceEnvironment.test.ts @@ -17,11 +17,13 @@ describe("mergeProviderInstanceEnvironment", () => { const baseEnv = { CODEX_HOME: "~/.inherited-codex", CLAUDE_CONFIG_DIR: "~/.inherited-claude", + GROK_HOME: "~/.inherited-grok", }; const environment = yield* mergeProviderInstanceEnvironment( [ { name: "CODEX_HOME", value, sensitive: false }, { name: "CLAUDE_CONFIG_DIR", value, sensitive: false }, + { name: "GROK_HOME", value, sensitive: false }, { name: "CUSTOM_VALUE", value, sensitive: false }, ], baseEnv, @@ -30,11 +32,13 @@ describe("mergeProviderInstanceEnvironment", () => { expect(environment).toEqual({ CODEX_HOME: path.join("/home/ada", tail), CLAUDE_CONFIG_DIR: path.join("/home/ada", tail), + GROK_HOME: path.join("/home/ada", tail), CUSTOM_VALUE: value, }); expect(baseEnv).toEqual({ CODEX_HOME: "~/.inherited-codex", CLAUDE_CONFIG_DIR: "~/.inherited-claude", + GROK_HOME: "~/.inherited-grok", }); }).pipe( Effect.provideService(HostProcess.HomeDirectory, "/home/ada"), diff --git a/packages/provider-core/src/server/instanceEnvironment.ts b/packages/provider-core/src/server/instanceEnvironment.ts index 470f7e70dc616..0ade572984275 100644 --- a/packages/provider-core/src/server/instanceEnvironment.ts +++ b/packages/provider-core/src/server/instanceEnvironment.ts @@ -2,7 +2,7 @@ import type { ProviderInstanceEnvironment } from "@t3tools/contracts"; import * as HostProcess from "@t3tools/shared/HostProcess"; import * as Effect from "effect/Effect"; -import { expandHomePath } from "./pathExpansion.ts"; +import { expandHomeVariable } from "./pathExpansion.ts"; export const mergeProviderInstanceEnvironment = Effect.fn(function* ( environment: ProviderInstanceEnvironment | undefined, @@ -27,10 +27,7 @@ export const mergeProviderInstanceEnvironment = Effect.fn(function* ( if (inherited.toUpperCase() === name) delete next[inherited]; } } - next[name] = - name === "CODEX_HOME" || name === "CLAUDE_CONFIG_DIR" - ? expandHomePath(variable.value, home) - : variable.value; + next[name] = expandHomeVariable(name, variable.value, home); } return next; }); diff --git a/packages/provider-core/src/server/pathExpansion.ts b/packages/provider-core/src/server/pathExpansion.ts index 9a3822b836312..6c5eab795000e 100644 --- a/packages/provider-core/src/server/pathExpansion.ts +++ b/packages/provider-core/src/server/pathExpansion.ts @@ -10,3 +10,9 @@ export function expandHomePath(value: string, home: string): string { } return value; } + +const HOME_PATH_VARIABLES = new Set(["CODEX_HOME", "CLAUDE_CONFIG_DIR", "GROK_HOME"]); + +export function expandHomeVariable(name: string, value: string, home: string): string { + return HOME_PATH_VARIABLES.has(name) ? expandHomePath(value, home) : value; +}