From e4bb2d59b5ec54e17bcfb7c51e62ca82efa227d9 Mon Sep 17 00:00:00 2001 From: Nathan Shan Date: Fri, 23 Oct 2026 20:07:54 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(git):=20=E5=85=81=E8=AE=B8=E6=98=BE?= =?UTF-8?q?=E5=BC=8F=E4=BF=9D=E7=95=99=E9=9D=9E=E4=BA=A4=E4=BA=92=E5=BC=8F?= =?UTF-8?q?=E5=87=AD=E6=8D=AE=E5=8A=A9=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增服务端环境开关,让后台和工作区准备 fetch 可保留继承的 GIT_ASKPASS,默认行为不变。 - 保持终端提示、Git Credential Manager 交互及 SSH askpass 禁用,并说明可信助手的适用范围。 - 通过真实 Git 与隔离 HTTP 认证测试验证开启、默认和关闭路径;165 项模块测试及 check:changed 通过。 --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 225 ++++++++++++++----- apps/server/src/vcs/GitVcsDriverCore.ts | 9 +- docs/user/source-control.md | 8 + 3 files changed, 183 insertions(+), 59 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 6bd0137154dad..e1f492d5ba651 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1,5 +1,6 @@ // @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not. import * as NodeFS from "node:fs"; +import * as NodeHttp from "node:http"; import * as NodeServices from "@effect/platform-node/NodeServices"; import * as HostProcess from "@t3tools/shared/HostProcess"; import { assert, it, describe } from "@effect/vitest"; @@ -2688,65 +2689,72 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }), ); - it.effect("makes background upstream status fetches non-interactive", () => - Effect.gen(function* () { - const cwd = yield* makeTmpDir(); - const tempDir = yield* makeTmpDir("git-vcs-driver-ssh-env-"); - const { initialBranch } = yield* initRepoWithCommit(cwd); - const fileSystem = yield* FileSystem.FileSystem; - const pathService = yield* Path.Path; - const sshLogPath = pathService.join(tempDir, "ssh-env.txt"); - const sshWrapperPath = yield* pathService.fromFileUrl( - new URL("./testing/GitVcsDriverCore.fixture.sh", import.meta.url), - ); - const envKeys = [ - "GCM_INTERACTIVE", - "GIT_ASKPASS", - "GIT_SSH", - "GIT_TERMINAL_PROMPT", - "SSH_ASKPASS", - "SSH_ASKPASS_REQUIRE", - "T3_TEST_SSH_ASKPASS_LOG", - ] as const; - const previousEnv = new Map(envKeys.map((key) => [key, process.env[key]])); - - yield* git(cwd, ["remote", "add", "origin", "ssh://example.invalid/repo.git"]); - yield* git(cwd, ["update-ref", `refs/remotes/origin/${initialBranch}`, "HEAD"]); - yield* git(cwd, ["branch", "--set-upstream-to", `origin/${initialBranch}`]); + it.effect.each([false, true])( + "makes background upstream status fetches non-interactive (preserve askpass: %s)", + (preserveAskpass) => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const tempDir = yield* makeTmpDir("git-vcs-driver-ssh-env-"); + const { initialBranch } = yield* initRepoWithCommit(cwd); + const fileSystem = yield* FileSystem.FileSystem; + const pathService = yield* Path.Path; + const sshLogPath = pathService.join(tempDir, "ssh-env.txt"); + const sshWrapperPath = yield* pathService.fromFileUrl( + new URL("./testing/GitVcsDriverCore.fixture.sh", import.meta.url), + ); + const envKeys = [ + "GCM_INTERACTIVE", + "GIT_ASKPASS", + "GIT_SSH", + "GIT_TERMINAL_PROMPT", + "SSH_ASKPASS", + "SSH_ASKPASS_REQUIRE", + "T3_TEST_SSH_ASKPASS_LOG", + ] as const; + const previousEnv = new Map(envKeys.map((key) => [key, process.env[key]])); + + yield* git(cwd, ["remote", "add", "origin", "ssh://example.invalid/repo.git"]); + yield* git(cwd, ["update-ref", `refs/remotes/origin/${initialBranch}`, "HEAD"]); + yield* git(cwd, ["branch", "--set-upstream-to", `origin/${initialBranch}`]); - yield* Effect.gen(function* () { - process.env.GIT_SSH = sshWrapperPath; - process.env.GCM_INTERACTIVE = "always"; - process.env.GIT_ASKPASS = "git-askpass"; - process.env.GIT_TERMINAL_PROMPT = "1"; - process.env.SSH_ASKPASS = "ssh-askpass"; - process.env.SSH_ASKPASS_REQUIRE = "force"; - process.env.T3_TEST_SSH_ASKPASS_LOG = sshLogPath; - - yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd); - - assert.deepEqual((yield* fileSystem.readFileString(sshLogPath)).trim().split(/\r?\n/), [ - "GCM_INTERACTIVE=never", - "GIT_ASKPASS=", - "GIT_TERMINAL_PROMPT=0", - "SSH_ASKPASS=", - "SSH_ASKPASS_REQUIRE=never", - ]); - }).pipe( - Effect.ensuring( - Effect.sync(() => { - for (const key of envKeys) { - const previous = previousEnv.get(key); - if (previous === undefined) { - delete process.env[key]; - } else { - process.env[key] = previous; + yield* Effect.gen(function* () { + process.env.GIT_SSH = sshWrapperPath; + process.env.GCM_INTERACTIVE = "always"; + process.env.GIT_ASKPASS = "git-askpass"; + process.env.GIT_TERMINAL_PROMPT = "1"; + process.env.SSH_ASKPASS = "ssh-askpass"; + process.env.SSH_ASKPASS_REQUIRE = "force"; + process.env.T3_TEST_SSH_ASKPASS_LOG = sshLogPath; + + yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd).pipe( + Effect.provideService(HostProcess.Environment, { + GIT_ASKPASS: "git-askpass", + T3CODE_GIT_PRESERVE_ASKPASS: preserveAskpass ? "1" : "0", + }), + ); + + assert.deepEqual((yield* fileSystem.readFileString(sshLogPath)).trim().split(/\r?\n/), [ + "GCM_INTERACTIVE=never", + `GIT_ASKPASS=${preserveAskpass ? "git-askpass" : ""}`, + "GIT_TERMINAL_PROMPT=0", + "SSH_ASKPASS=", + "SSH_ASKPASS_REQUIRE=never", + ]); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + for (const key of envKeys) { + const previous = previousEnv.get(key); + if (previous === undefined) { + delete process.env[key]; + } else { + process.env[key] = previous; + } } - } - }), - ), - ); - }), + }), + ), + ); + }), ); it.effect("reuses the no-upstream fallback ahead count for default-branch delta", () => @@ -3952,6 +3960,109 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }); describe("remote operations", () => { + it.effect.skipIf(HostProcess.Platform.defaultValue() === "win32")( + "fetches an authenticated remote with an explicitly enabled non-interactive askpass helper", + () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const cwd = yield* makeTmpDir(); + const remote = yield* makeTmpDir("git-askpass-remote-"); + const { initialBranch } = yield* initRepoWithCommit(remote); + const remoteHead = yield* git(remote, ["rev-parse", "HEAD"]); + yield* git(remote, ["update-server-info"]); + yield* initRepoWithCommit(cwd); + // Never consult the developer's credential helpers or proxy for this fixture. + yield* git(cwd, ["config", "credential.helper", ""]); + yield* git(cwd, ["config", "core.askPass", ""]); + yield* git(cwd, ["config", "http.proxy", ""]); + const helper = path.join(remote, "askpass.sh"); + const helperLog = `${helper}.log`; + yield* fs.writeFileString( + helper, + `#!/bin/sh\nprintf 'used\\n' >> "$0.log"\nprintf 'fixture\\n'\n`, + { mode: 0o700 }, + ); + const gitDirectory = path.join(remote, ".git"); + const server = NodeHttp.createServer((request, response) => { + if ( + request.headers.authorization !== + `Basic ${Buffer.from("fixture:fixture").toString("base64")}` + ) { + response.writeHead(401, { "WWW-Authenticate": 'Basic realm="git fixture"' }); + response.end(); + return; + } + const requested = path.resolve( + gitDirectory, + `.${new URL(request.url ?? "/", "http://localhost").pathname}`, + ); + if (!requested.startsWith(`${gitDirectory}${path.sep}`)) { + response.writeHead(404).end(); + return; + } + try { + const contents = NodeFS.readFileSync(requested); + response.writeHead(200, { "Content-Type": "text/plain" }); + response.end(contents); + } catch { + response.writeHead(404).end(); + } + }); + yield* Effect.acquireRelease( + Effect.promise( + () => + new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }), + ), + () => + Effect.promise(() => new Promise((resolve) => server.close(() => resolve()))), + ); + const address = server.address(); + assert.isNotNull(address); + if (address === null || typeof address === "string") return; + yield* git(cwd, ["remote", "add", "origin", `http://127.0.0.1:${address.port}/`]); + const driver = yield* GitVcsDriver.GitVcsDriver; + const fetch = driver.fetchRemote({ cwd, remoteName: "origin" }); + const inherited = { GIT_ASKPASS: helper }; + assert.isTrue( + Result.isFailure( + yield* Effect.result( + fetch.pipe(Effect.provideService(HostProcess.Environment, inherited)), + ), + ), + ); + assert.isFalse(yield* fs.exists(helperLog)); + yield* fetch.pipe( + Effect.provideService(HostProcess.Environment, { + ...inherited, + T3CODE_GIT_PRESERVE_ASKPASS: "1", + }), + ); + assert.strictEqual( + yield* git(cwd, ["rev-parse", `refs/remotes/origin/${initialBranch}`]), + remoteHead, + ); + assert.isTrue(yield* fs.exists(helperLog)); + const invocations = yield* fs.readFileString(helperLog); + assert.isTrue( + Result.isFailure( + yield* Effect.result( + fetch.pipe( + Effect.provideService(HostProcess.Environment, { + ...inherited, + T3CODE_GIT_PRESERVE_ASKPASS: "0", + }), + ), + ), + ), + ); + assert.strictEqual(yield* fs.readFileString(helperLog), invocations); + }), + ); + it.effect("creates a worktree from the latest fetched remote commit", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index b5a523876a58e..c112ad3eaf940 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -104,6 +104,11 @@ const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({ SSH_ASKPASS: "", SSH_ASKPASS_REQUIRE: "never", } satisfies NodeJS.ProcessEnv); +const upstreamRefreshEnvironment = Effect.map(HostProcess.Environment, (environment) => + environment.T3CODE_GIT_PRESERVE_ASKPASS === "1" + ? { ...STATUS_UPSTREAM_REFRESH_ENV, GIT_ASKPASS: environment.GIT_ASKPASS ?? "" } + : STATUS_UPSTREAM_REFRESH_ENV, +); const DEFAULT_BASE_BRANCH_CANDIDATES = ["main", "master"] as const; const GIT_LIST_BRANCHES_DEFAULT_LIMIT = 100; const NON_REPOSITORY_STATUS_DETAILS = Object.freeze({ @@ -1326,7 +1331,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* // healthy writer. A separate safety limit prevents a hung transport from // occupying its worker slot indefinitely. const started = yield* executeGit("GitVcsDriver.fetchRemoteForStatus", fetchCwd, args, { - env: STATUS_UPSTREAM_REFRESH_ENV, + env: yield* upstreamRefreshEnvironment, fallbackErrorDetail: "Background Git fetch exited with a non-zero status.", timeoutMs: Duration.toMillis(STATUS_UPSTREAM_FETCH_TIMEOUT), forceKillAfterMs: 1_000, @@ -3989,7 +3994,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const fetchRemote = Effect.fn("fetchRemote")(function* (input: GitVcsDriver.GitFetchRemoteInput) { const args = ["fetch", "--quiet", "--", input.remoteName]; const options = { - env: STATUS_UPSTREAM_REFRESH_ENV, + env: yield* upstreamRefreshEnvironment, fallbackErrorDetail: `git fetch ${input.remoteName} failed`, }; // Another fetch in the same repository, such as the background status diff --git a/docs/user/source-control.md b/docs/user/source-control.md index cb323ea4c3bf2..c1108c6ae21d9 100644 --- a/docs/user/source-control.md +++ b/docs/user/source-control.md @@ -16,6 +16,14 @@ Install Git and configure authentication on the machine running your T3 Code ser environment, do this on the remote machine. After signing in, open **Settings → Source Control** and choose **Rescan**. +If your server environment supplies Git credentials through a trusted, non-interactive +`GIT_ASKPASS` helper, set `T3CODE_GIT_PRESERVE_ASKPASS=1` in that environment before starting +T3 Code. This preserves the helper for background fetches and workspace preparation fetches. +Terminal prompting, Git Credential Manager dialogs, and SSH askpass remain disabled. Leave +the option unset to keep the default behavior, which disables Git askpass too. Only enable +it for a helper that returns credentials without user interaction; T3 Code cannot prevent +the helper itself from opening a dialog. + ### GitHub T3 Code talks to GitHub's API directly and only needs a token. Any of these works, in this From c3b9eb77544f0f9a2c6db227864243e2ee14dec2 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sat, 31 Oct 2026 00:25:34 +0000 Subject: [PATCH 2/3] test(git): simplify askpass coverage and reject invalid opt-in values --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 61 +++++++++----------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 532d715a8f2b0..1a812b6c31114 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -5000,40 +5000,35 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { yield* git(cwd, ["remote", "add", "origin", `http://127.0.0.1:${address.port}/`]); const driver = yield* GitVcsDriver.GitVcsDriver; const fetch = driver.fetchRemote({ cwd, remoteName: "origin" }); - const inherited = { GIT_ASKPASS: helper }; - assert.isTrue( - Result.isFailure( - yield* Effect.result( - fetch.pipe(Effect.provideService(HostProcess.Environment, inherited)), - ), - ), - ); - assert.isFalse(yield* fs.exists(helperLog)); - yield* fetch.pipe( - Effect.provideService(HostProcess.Environment, { - ...inherited, - T3CODE_GIT_PRESERVE_ASKPASS: "1", - }), - ); - assert.strictEqual( - yield* git(cwd, ["rev-parse", `refs/remotes/origin/${initialBranch}`]), - remoteHead, - ); - assert.isTrue(yield* fs.exists(helperLog)); - const invocations = yield* fs.readFileString(helperLog); - assert.isTrue( - Result.isFailure( - yield* Effect.result( - fetch.pipe( - Effect.provideService(HostProcess.Environment, { - ...inherited, - T3CODE_GIT_PRESERVE_ASKPASS: "0", - }), - ), + let invocations = ""; + for (const setting of [undefined, "1", "0", "true"]) { + const result = yield* Effect.result( + fetch.pipe( + Effect.provideService(HostProcess.Environment, { + GIT_ASKPASS: helper, + T3CODE_GIT_PRESERVE_ASKPASS: setting, + }), ), - ), - ); - assert.strictEqual(yield* fs.readFileString(helperLog), invocations); + ); + if (setting === "1") { + assert.isTrue(Result.isSuccess(result)); + assert.strictEqual( + yield* git(cwd, ["rev-parse", `refs/remotes/origin/${initialBranch}`]), + remoteHead, + ); + invocations = yield* fs.readFileString(helperLog); + assert.isNotEmpty(invocations); + } else { + assert.isTrue(Result.isFailure(result)); + if (Result.isFailure(result)) { + assert.include(result.failure.detail, "Git could not authenticate with the remote"); + } + assert.strictEqual( + (yield* fs.exists(helperLog)) ? yield* fs.readFileString(helperLog) : "", + invocations, + ); + } + } }), ); From d65963400227bc46e6ab65a8a1e44fef8d2585bd Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 22:05:06 +0000 Subject: [PATCH 3/3] test(git): cover the workspace fetch with the askpass env fixture Replace the HTTP-server integration test with a fetchRemote assertion in the existing environment-log test, and tighten the user docs paragraph. AI-assisted; verified with the tests and tools listed above. --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 119 +++---------------- docs/user/source-control.md | 12 +- 2 files changed, 19 insertions(+), 112 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 0fefa8608f63a..d5440408ca65e 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1,6 +1,5 @@ // @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not. import * as NodeFS from "node:fs"; -import * as NodeHttp from "node:http"; import * as NodePath from "node:path"; import * as NodeURL from "node:url"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; @@ -4532,7 +4531,7 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { ); it.effect.each([false, true])( - "makes background upstream status fetches non-interactive (preserve askpass: %s)", + "makes background and workspace fetches non-interactive (preserve askpass: %s)", (preserveAskpass) => Effect.gen(function* () { const cwd = yield* makeTmpDir(); @@ -4550,15 +4549,24 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { yield* git(cwd, ["branch", "--set-upstream-to", `origin/${initialBranch}`]); yield* Effect.gen(function* () { - yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd); - - assert.deepEqual((yield* fileSystem.readFileString(sshLogPath)).trim().split(/\r?\n/), [ + const driver = yield* GitVcsDriver.GitVcsDriver; + const expected = [ "GCM_INTERACTIVE=never", `GIT_ASKPASS=${preserveAskpass ? "git-askpass" : ""}`, "GIT_TERMINAL_PROMPT=0", "SSH_ASKPASS=", "SSH_ASKPASS_REQUIRE=never", - ]); + ]; + const readLog = fileSystem + .readFileString(sshLogPath) + .pipe(Effect.map((log) => log.trim().split(/\r?\n/))); + + yield* driver.statusDetails(cwd); + assert.deepEqual(yield* readLog, expected); + yield* fileSystem.remove(sshLogPath); + + yield* Effect.result(driver.fetchRemote({ cwd, remoteName: "origin" })); + assert.deepEqual(yield* readLog, expected); }).pipe( Effect.provideService(HostProcess.Environment, { ...(yield* HostProcess.Environment), @@ -6892,105 +6900,6 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => { }); describe("remote operations", () => { - it.effect.skipIf(HostProcess.Platform.defaultValue() === "win32")( - "fetches an authenticated remote with an explicitly enabled non-interactive askpass helper", - () => - Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - const cwd = yield* makeTmpDir(); - const remote = yield* makeTmpDir("git-askpass-remote-"); - const { initialBranch } = yield* initRepoWithCommit(remote); - const remoteHead = yield* git(remote, ["rev-parse", "HEAD"]); - yield* git(remote, ["update-server-info"]); - yield* initRepoWithCommit(cwd); - // Never consult the developer's credential helpers or proxy for this fixture. - yield* git(cwd, ["config", "credential.helper", ""]); - yield* git(cwd, ["config", "core.askPass", ""]); - yield* git(cwd, ["config", "http.proxy", ""]); - const helper = path.join(remote, "askpass.sh"); - const helperLog = `${helper}.log`; - yield* fs.writeFileString( - helper, - `#!/bin/sh\nprintf 'used\\n' >> "$0.log"\nprintf 'fixture\\n'\n`, - { mode: 0o700 }, - ); - const gitDirectory = path.join(remote, ".git"); - const server = NodeHttp.createServer((request, response) => { - if ( - request.headers.authorization !== - `Basic ${Buffer.from("fixture:fixture").toString("base64")}` - ) { - response.writeHead(401, { "WWW-Authenticate": 'Basic realm="git fixture"' }); - response.end(); - return; - } - const requested = path.resolve( - gitDirectory, - `.${new URL(request.url ?? "/", "http://localhost").pathname}`, - ); - if (!requested.startsWith(`${gitDirectory}${path.sep}`)) { - response.writeHead(404).end(); - return; - } - try { - const contents = NodeFS.readFileSync(requested); - response.writeHead(200, { "Content-Type": "text/plain" }); - response.end(contents); - } catch { - response.writeHead(404).end(); - } - }); - yield* Effect.acquireRelease( - Effect.promise( - () => - new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", resolve); - }), - ), - () => - Effect.promise(() => new Promise((resolve) => server.close(() => resolve()))), - ); - const address = server.address(); - assert.isNotNull(address); - if (address === null || typeof address === "string") return; - yield* git(cwd, ["remote", "add", "origin", `http://127.0.0.1:${address.port}/`]); - const driver = yield* GitVcsDriver.GitVcsDriver; - const fetch = driver.fetchRemote({ cwd, remoteName: "origin" }); - let invocations = ""; - for (const setting of [undefined, "1", "0", "true"]) { - const result = yield* Effect.result( - fetch.pipe( - Effect.provideService(HostProcess.Environment, { - ...(yield* HostProcess.Environment), - GIT_ASKPASS: helper, - T3CODE_GIT_PRESERVE_ASKPASS: setting, - }), - ), - ); - if (setting === "1") { - assert.isTrue(Result.isSuccess(result)); - assert.strictEqual( - yield* git(cwd, ["rev-parse", `refs/remotes/origin/${initialBranch}`]), - remoteHead, - ); - invocations = yield* fs.readFileString(helperLog); - assert.isNotEmpty(invocations); - } else { - assert.isTrue(Result.isFailure(result)); - if (Result.isFailure(result)) { - assert.include(result.failure.detail, "Git could not authenticate with the remote"); - } - assert.strictEqual( - (yield* fs.exists(helperLog)) ? yield* fs.readFileString(helperLog) : "", - invocations, - ); - } - } - }), - ); - it.effect("creates a worktree from the latest fetched remote commit", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/docs/user/source-control.md b/docs/user/source-control.md index a7f9c6053a2e9..03987139b8498 100644 --- a/docs/user/source-control.md +++ b/docs/user/source-control.md @@ -26,13 +26,11 @@ Install Git and configure authentication on the machine running your T3 Code ser environment, do this on the remote machine. After signing in, open **Settings → Source control** and choose **Rescan**. -If your server environment supplies Git credentials through a trusted, non-interactive -`GIT_ASKPASS` helper, set `T3CODE_GIT_PRESERVE_ASKPASS=1` in that environment before starting -T3 Code. This preserves the helper for background fetches and workspace preparation fetches. -Terminal prompting, Git Credential Manager dialogs, and SSH askpass remain disabled. Leave -the option unset to keep the default behavior, which disables Git askpass too. Only enable -it for a helper that returns credentials without user interaction; T3 Code cannot prevent -the helper itself from opening a dialog. +Background fetches and workspace preparation never prompt for credentials, so T3 Code clears +`GIT_ASKPASS` for them. If your server supplies credentials through a non-interactive `GIT_ASKPASS` +helper, set `T3CODE_GIT_PRESERVE_ASKPASS=1` in the server's environment (for the background +service, in [`service.env`](./background-service.md#configure-the-service-environment)) to keep +it. Only use this for a helper that never opens a dialog. ### GitHub