diff --git a/apps/server/src/os-jank.test.ts b/apps/server/src/os-jank.test.ts index 0051747e2195..bfe12f57d103 100644 --- a/apps/server/src/os-jank.test.ts +++ b/apps/server/src/os-jank.test.ts @@ -74,28 +74,85 @@ effectIt.effect.each(["darwin", "linux"] as const)( ), ); -effectIt.effect("hydrates PATH when the desktop handoff is absent", () => - Effect.acquireUseRelease( - Effect.sync(() => - vi - .spyOn(NodeChildProcess, "execFileSync") - .mockReturnValue("__T3CODE_ENV_PATH_START__\n/opt/tools/bin\n__T3CODE_ENV_PATH_END__\n"), - ), - (probe) => - Effect.gen(function* () { - const env: NodeJS.ProcessEnv = { SHELL: "/bin/bash", PATH: "/usr/bin" }; +effectIt.effect.each([ + { + name: "recovers the login-shell agent on Linux", + platform: "linux", + shellPath: "/opt/tools/bin", + shellSocket: "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/login/agent.sock", + inheritedSocket: undefined, + expectedSocket: "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/login/agent.sock", + }, + { + name: "recovers the login-shell agent on macOS", + platform: "darwin", + shellPath: "/opt/tools/bin", + shellSocket: "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/login/agent.sock", + inheritedSocket: undefined, + expectedSocket: "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/login/agent.sock", + }, + { + name: "preserves an explicitly forwarded agent", + platform: "linux", + shellPath: "/opt/tools/bin", + shellSocket: "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/login/agent.sock", + inheritedSocket: "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/forwarded/agent.sock", + expectedSocket: "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/forwarded/agent.sock", + }, + { + name: "leaves the agent absent when only a rejected shell exports one", + platform: "linux", + shellPath: "/opt/tools/bin", + shellSocket: undefined, + inheritedSocket: undefined, + expectedSocket: undefined, + }, + { + name: "leaves the agent absent when no shell provides PATH", + platform: "linux", + shellPath: undefined, + shellSocket: "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/login/agent.sock", + inheritedSocket: undefined, + expectedSocket: undefined, + }, +] as const)( + "$name without a prepared shell environment", + ({ platform, shellPath, shellSocket, inheritedSocket, expectedSocket }) => + Effect.acquireUseRelease( + Effect.sync(() => + vi + .spyOn(NodeChildProcess, "execFileSync") + .mockReturnValueOnce( + "__T3CODE_ENV_SSH_AUTH_SOCK_START__\n/rejected/agent.sock\n__T3CODE_ENV_SSH_AUTH_SOCK_END__\n", + ) + .mockReturnValue( + [ + "__T3CODE_ENV_PATH_START__", + shellPath ?? "", + "__T3CODE_ENV_PATH_END__", + "__T3CODE_ENV_SSH_AUTH_SOCK_START__", + shellSocket ?? "", + "__T3CODE_ENV_SSH_AUTH_SOCK_END__", + ].join("\n"), + ), + ), + (probe) => + Effect.gen(function* () { + const env: NodeJS.ProcessEnv = { SHELL: "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/custom/shell", PATH: "/usr/bin" }; + if (inheritedSocket) env.SSH_AUTH_SOCK = inheritedSocket; - yield* fixPath().pipe( - Effect.provideService(HostProcess.Platform, "linux"), - Effect.provideService(HostProcess.Environment, env), - Effect.provide(NodeServices.layer), - ); + yield* fixPath().pipe( + Effect.provideService(HostProcess.Platform, platform), + Effect.provideService(HostProcess.Environment, env), + Effect.provide(NodeServices.layer), + ); - assert.equal(probe.mock.calls.length, 1); - assert.equal(env.PATH, "/opt/tools/bin:/usr/bin"); - }), - (probe) => Effect.sync(() => probe.mockRestore()), - ), + if (shellPath) assert.equal(probe.mock.calls.length, 2); + assert.equal(env.PATH, shellPath ? "/opt/tools/bin:/usr/bin" : "/usr/bin"); + assert.equal(env.SSH_AUTH_SOCK, expectedSocket); + }), + (probe) => Effect.sync(() => probe.mockRestore()), + ), ); effectIt.effect("keeps mise's PATH record with the login shell PATH", () => @@ -124,6 +181,10 @@ effectIt.effect("keeps mise's PATH record with the login shell PATH", () => Effect.provide(NodeServices.layer), ); + assert.equal( + env.PATH, + "/home/me/.local/bin:/mise/installs/claude/latest:/mise/shims:/usr/bin", + ); assert.equal(env.__MISE_DIFF, "mise-diff"); }), (probe) => Effect.sync(() => probe.mockRestore()), diff --git a/apps/server/src/os-jank.ts b/apps/server/src/os-jank.ts index b6d0d5a7ad72..b1340dd5c878 100644 --- a/apps/server/src/os-jank.ts +++ b/apps/server/src/os-jank.ts @@ -25,7 +25,11 @@ function hydratePosixPath(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): vo // mise's record of the PATH entries it added. It must travel with the // shell's PATH, or `mise x` can rebuild PATH behind a same-named wrapper // (`exec mise x claude -- claude`) that then re-runs itself forever. - shellEnvironment = readEnvironmentFromLoginShell(shell, ["PATH", "__MISE_DIFF"]); + shellEnvironment = readEnvironmentFromLoginShell(shell, [ + "PATH", + "__MISE_DIFF", + "SSH_AUTH_SOCK", + ]); } catch (error) { logPathHydrationWarning(`Failed to read PATH from login shell ${shell}.`, error); } @@ -39,6 +43,9 @@ function hydratePosixPath(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): vo if (mergedPath) { env.PATH = mergedPath; } + if (shellPath && !env.SSH_AUTH_SOCK && shellEnvironment.SSH_AUTH_SOCK) { + env.SSH_AUTH_SOCK = shellEnvironment.SSH_AUTH_SOCK; + } if (shellPath && shellEnvironment.__MISE_DIFF) { env.__MISE_DIFF = shellEnvironment.__MISE_DIFF; }