From 9ec7039d2865ffc5fb7bef67ff9fbd11765d96b2 Mon Sep 17 00:00:00 2001 From: Michael Charles Aubrey Date: Tue, 1 Sep 2026 16:30:08 +0900 Subject: [PATCH] fix(desktop): forward Bitbucket credentials from the login shell A GUI launch on macOS and Linux inherits nothing from ~/.zshrc or ~/.profile, so T3CODE_BITBUCKET_* exports never reach the desktop app. The login-shell probe already runs at startup, but it only asks for the names in LOGIN_SHELL_ENV_NAMES, so these credentials were in scope for the probe and never requested. Bitbucket then reports as unauthenticated even though the same credentials work in a server started from a terminal. Add the three credential names the server reads to the probe list and hydrate them as one precedence group. The group matters because an access token outranks an email plus API token, so adding a shell-probed pair next to an inherited access token would leave the probed values silently unused. Fixes #5840 --- .../src/shell/DesktopShellEnvironment.test.ts | 53 +++++++++++++++++++ .../src/shell/DesktopShellEnvironment.ts | 23 ++++++++ 2 files changed, 76 insertions(+) diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts index 5a76402b1d34..871e5390b8b7 100644 --- a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts +++ b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts @@ -152,6 +152,59 @@ describe("DesktopShellEnvironment", () => { }), ); + it.effect("hydrates Bitbucket credentials from the login shell on macOS", () => + Effect.gen(function* () { + const env: NodeJS.ProcessEnv = { + SHELL: "/bin/zsh", + PATH: "/usr/bin", + }; + + yield* runShellEnvironment({ + env, + platform: "darwin", + handler: () => + envOutput({ + PATH: "/opt/homebrew/bin:/usr/bin", + T3CODE_BITBUCKET_ACCESS_TOKEN: "access-token", + T3CODE_BITBUCKET_EMAIL: "developer@example.com", + T3CODE_BITBUCKET_API_TOKEN: "api-token", + }), + }); + + assert.equal(env.T3CODE_BITBUCKET_ACCESS_TOKEN, "access-token"); + assert.equal(env.T3CODE_BITBUCKET_EMAIL, "developer@example.com"); + assert.equal(env.T3CODE_BITBUCKET_API_TOKEN, "api-token"); + }), + ); + + it.effect("does not mix login-shell Bitbucket credentials into an inherited set", () => + Effect.gen(function* () { + const env: NodeJS.ProcessEnv = { + SHELL: "/bin/zsh", + PATH: "/usr/bin", + T3CODE_BITBUCKET_ACCESS_TOKEN: "inherited-access-token", + }; + + yield* runShellEnvironment({ + env, + platform: "darwin", + handler: () => + envOutput({ + PATH: "/opt/homebrew/bin:/usr/bin", + T3CODE_BITBUCKET_EMAIL: "developer@example.com", + T3CODE_BITBUCKET_API_TOKEN: "api-token", + }), + }); + + // An access token outranks email plus API token, so hydrating the pair here + // would leave the app authenticating with the inherited token and the shell + // credentials silently ignored. + assert.equal(env.T3CODE_BITBUCKET_ACCESS_TOKEN, "inherited-access-token"); + assert.equal(env.T3CODE_BITBUCKET_EMAIL, undefined); + assert.equal(env.T3CODE_BITBUCKET_API_TOKEN, undefined); + }), + ); + it.effect("hydrates the locale from the login shell on macOS", () => Effect.gen(function* () { const env: NodeJS.ProcessEnv = { diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.ts b/apps/desktop/src/shell/DesktopShellEnvironment.ts index b4610eee5c84..97c7ffa1c88d 100644 --- a/apps/desktop/src/shell/DesktopShellEnvironment.ts +++ b/apps/desktop/src/shell/DesktopShellEnvironment.ts @@ -67,6 +67,16 @@ export class DesktopShellEnvironment extends Context.Service< } >()("@t3tools/desktop/shell/DesktopShellEnvironment") {} +// The server reads these from plain process env, and we document them as shell +// exports. A GUI launch inherits nothing from the shell, so without forwarding +// them the desktop app reports Bitbucket as unauthenticated while the very same +// credentials work in a terminal-started server. +const BITBUCKET_CREDENTIAL_ENV_NAMES = [ + "T3CODE_BITBUCKET_ACCESS_TOKEN", + "T3CODE_BITBUCKET_EMAIL", + "T3CODE_BITBUCKET_API_TOKEN", +] as const; + const LOGIN_SHELL_ENV_NAMES = [ "PATH", "DBUS_SESSION_BUS_ADDRESS", @@ -85,6 +95,7 @@ const LOGIN_SHELL_ENV_NAMES = [ "XDG_SESSION_DESKTOP", "XDG_SESSION_TYPE", "WAYLAND_DISPLAY", + ...BITBUCKET_CREDENTIAL_ENV_NAMES, ] as const; const WINDOWS_PROFILE_ENV_NAMES = ["PATH", "FNM_DIR", "FNM_MULTISHELL_PATH"] as const; const LOCALE_ENV_NAMES = ["LANG", "LC_ALL", "LC_CTYPE"] as const; @@ -480,6 +491,18 @@ const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosix } } + // Bitbucket credentials form one precedence group: an access token outranks an + // email plus API token, so adding a shell-probed pair next to an inherited + // access token would leave the probed values silently unused. Take the whole + // set from one source, and prefer whatever the process already carries. + if (BITBUCKET_CREDENTIAL_ENV_NAMES.every((name) => !config.env[name])) { + for (const name of BITBUCKET_CREDENTIAL_ENV_NAMES) { + if (shellEnvironment[name]) { + config.env[name] = shellEnvironment[name]; + } + } + } + // Locale variables form one precedence group: LC_ALL can override an inherited // LANG or LC_CTYPE, so only hydrate the group when the process has none of them. if (