From 18f5fa51229da4e13a8a738fcf0d86af51c535d9 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sun, 1 Nov 2026 15:41:30 +0000 Subject: [PATCH 1/2] test(desktop): reproduce #28714, shell env cache miss reported as prepared A cache miss in installIntoProcess yields true before anything reaches process.env, so the server skips its own PATH hydration. These tests reproduce #28714 and fail on main. Co-Authored-By: Claude Opus 5.5 --- apps/desktop/src/shell/DesktopShellEnvironment.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts index 79344567fcd6c..b5bde775927f3 100644 --- a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts +++ b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts @@ -4,6 +4,7 @@ import { assert, describe, it } from "@effect/vitest"; import * as HostProcess from "@t3tools/shared/HostProcess"; import * as Effect from "effect/Effect"; import * as Deferred from "effect/Deferred"; +import * as Fiber from "effect/Fiber"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Logger from "effect/Logger"; @@ -164,6 +165,10 @@ describe("DesktopShellEnvironment", () => { const startup = yield* service.installIntoProcess.pipe(Effect.forkChild); yield* Deferred.await(started); assert.isDefined(startup.pollUnsafe()); + assert.isFalse( + yield* Fiber.join(startup), + "nothing was installed yet, so the server must hydrate PATH itself", + ); yield* Deferred.succeed(release, undefined); return yield* service.awaitDiscovery; }), @@ -231,7 +236,7 @@ describe("DesktopShellEnvironment", () => { }), use: (service) => Effect.gen(function* () { - yield* service.installIntoProcess; + assert.isTrue(yield* service.installIntoProcess); yield* service.installIntoProcess; assert.equal(env.PATH, "/old/bin:/usr/bin:/launcher/bin"); assert.equal(env.SSH_AUTH_SOCK, "/current-session.sock"); From 8dc3e6ff446dc1f1a714288091249ee86c91735b Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sun, 1 Nov 2026 15:49:01 +0000 Subject: [PATCH 2/2] fix(desktop): let the server hydrate PATH when the shell env cache misses installIntoProcess reported the shell environment as prepared even on a cache miss, before anything reached process.env. The server then skipped its own PATH hydration and probed providers with the bare launcher PATH, reporting login-shell-only CLIs as missing for the session. Yield true only when a cached environment was applied. Discovery still runs in the background in both cases, so startup stays non-blocking. Fixes pingdotgg/t3code#28714 Co-Authored-By: Claude Opus 5.5 --- apps/desktop/src/shell/DesktopShellEnvironment.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.ts b/apps/desktop/src/shell/DesktopShellEnvironment.ts index 556f7090b422e..dff70213ca74d 100644 --- a/apps/desktop/src/shell/DesktopShellEnvironment.ts +++ b/apps/desktop/src/shell/DesktopShellEnvironment.ts @@ -75,7 +75,8 @@ export class DesktopShellEnvironmentCommandTimeoutError extends Schema.TaggedErr export class DesktopShellEnvironment extends Context.Service< DesktopShellEnvironment, { - // Desktop owns discovery, including on cache misses; the backend receives updates over fd4. + // Yields true only when a cached environment was applied; on a miss the backend hydrates + // PATH itself until desktop discovery arrives over fd4. readonly installIntoProcess: Effect.Effect; readonly awaitDiscovery: Effect.Effect; readonly updates: Stream.Stream; @@ -622,7 +623,8 @@ export const make = Effect.gen(function* () { const cache = yield* fileSystem .readFileString(cachePath) .pipe(Effect.flatMap(decodeCache), Effect.option); - if (Option.isSome(cache) && cache.value.key === key) { + const hit = Option.isSome(cache) && cache.value.key === key; + if (hit) { const cached = Object.fromEntries( CACHED_ENV_NAMES.flatMap((name) => { const value = cache.value.environment[name]; @@ -648,7 +650,7 @@ export const make = Effect.gen(function* () { ); } yield* Effect.forkIn(refresh, scope); - return true; + return hit; }).pipe(Effect.withSpan("desktop.shellEnvironment.installIntoProcess")), ); const updates = Stream.unwrap(