From 1a331e285b7ded48913fa23e1710fb4c80aab811 Mon Sep 17 00:00:00 2001
From: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Date: Mon, 28 Sep 2026 13:13:09 -0700
Subject: [PATCH 1/3] fix(server): stop reporting a separate git dir as a
branch's worktree
Motivation: With a separate git dir that has no core.worktree back-link
(e.g. `git init --separate-git-dir`), `git --git-dir=
worktree list`
names the git dir itself as the main worktree. listRefs kept that path
because it exists on disk, so the branch's worktreePath pointed at the git
directory (objects/, refs/, HEAD) instead of the folder the user opened,
and it could be picked up as an existing worktree to reuse.
Approach: in readGitRefsSnapshot, drop worktree entries whose path equals
the git common dir. Normal repos (common dir is /.git), bare repos
and linked worktrees are unaffected.
Validation: added a real-git test using `git init --separate-git-dir`.
Ran `vp test run src/vcs` in apps/server (7 files, 214 tests pass). The new
test fails without the source change (worktreePath equals the git dir) and
passes with it. Also ran vp fmt and vp lint on the touched files.
Scope: this is a partial fix for the report. It only changes the worktree
paths returned by listRefs; project-root detection elsewhere
(resolveRepositoryPaths, --show-toplevel) is unchanged, and I did not
exercise the desktop or mobile clients.
Report: https://github.com/pingdotgg/t3code/issues/12204
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Assisted-by: claude-sonnet-5-5 (via Claude Code)
---
apps/server/src/vcs/GitVcsDriverCore.test.ts | 28 ++++++++++++++++++++
apps/server/src/vcs/GitVcsDriverCore.ts | 4 +++
2 files changed, 32 insertions(+)
diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts
index 85c4d1a59d1d..de546ade933c 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.test.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts
@@ -694,6 +694,34 @@ it.effect("ignores worktree metadata for directories that no longer exist", () =
).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
);
+it.effect("does not report a separate git dir as the worktree of its branch", () =>
+ Effect.scoped(
+ Effect.gen(function* () {
+ const driver = yield* makeGitVcsDriverCore();
+ const cwd = yield* makeTmpDir();
+ const gitDir = NodeFS.realpathSync(yield* makeTmpDir());
+ yield* git(cwd, ["init", `--separate-git-dir=${gitDir}`]).pipe(
+ Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
+ );
+ yield* git(cwd, ["config", "user.email", "test@example.com"]).pipe(
+ Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
+ );
+ yield* git(cwd, ["config", "user.name", "Test"]).pipe(
+ Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
+ );
+ yield* git(cwd, ["commit", "--allow-empty", "-m", "init"]).pipe(
+ Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
+ );
+ const refs = yield* driver.listRefs({ cwd, refresh: true });
+
+ assert.isTrue(refs.isRepo);
+ const current = refs.refs.find((ref) => ref.current);
+ assert.isDefined(current);
+ assert.notEqual(current?.worktreePath, gitDir);
+ }),
+ ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
+);
+
it.effect("refreshes the current branch after an external checkout", () =>
Effect.scoped(
Effect.gen(function* () {
diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts
index 0dd73af687f9..48c701039f59 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.ts
@@ -2829,9 +2829,13 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
[branchName, path.normalize(path.resolve(worktreePath))] as const,
)
: [];
+ // With a separate git dir lacking `core.worktree`, git lists the git dir itself as the
+ // main worktree. It is not a checkout, so it must never be offered as one.
+ const normalizedGitCommonDir = path.normalize(path.resolve(gitCommonDir));
const existingWorktreeEntries = yield* Effect.filter(
parsedWorktreeEntries,
([, worktreePath]) =>
+ worktreePath !== normalizedGitCommonDir &&
fileSystem.stat(worktreePath).pipe(
Effect.as(true),
Effect.orElseSucceed(() => false),
From 4dae5c76e78e80153abc76c0052dac18c88c63ad Mon Sep 17 00:00:00 2001
From: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Date: Tue, 6 Oct 2026 07:36:22 -0700
Subject: [PATCH 2/3] fix(server): keep the opened folder as the current
branch's worktreePath
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
---
apps/server/src/vcs/GitVcsDriverCore.test.ts | 1 +
apps/server/src/vcs/GitVcsDriverCore.ts | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts
index de546ade933c..eca34f9d994e 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.test.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts
@@ -718,6 +718,7 @@ it.effect("does not report a separate git dir as the worktree of its branch", ()
const current = refs.refs.find((ref) => ref.current);
assert.isDefined(current);
assert.notEqual(current?.worktreePath, gitDir);
+ assert.equal(current?.worktreePath, NodeFS.realpathSync(cwd));
}),
).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
);
diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts
index 48c701039f59..afe08ff5c913 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.ts
@@ -3030,6 +3030,13 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
snapshot.localBranches.some((ref) => ref.worktreePath === repositoryPaths.worktreeRoot);
const localBranches = snapshot.localBranches.map((ref) => ({
...ref,
+ // A separate git dir is filtered out of the worktree list, which leaves the
+ // current branch without a path; fall back to the folder the user opened.
+ worktreePath:
+ ref.worktreePath ??
+ (!ref.isRemote && ref.name === repositoryPaths.currentBranch
+ ? repositoryPaths.worktreeRoot
+ : null),
current: hasCurrentWorktreeBranch
? ref.worktreePath === repositoryPaths.worktreeRoot
: ref.name === repositoryPaths.currentBranch,
From c44eed3aebc2a6e2616aa5866182449e860ec079 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Mon, 19 Oct 2026 05:15:00 +0000
Subject: [PATCH 3/3] fix(server): preserve checkout paths with separate Git
directories
---
apps/server/src/vcs/GitVcsDriverCore.test.ts | 33 ++++++++------------
apps/server/src/vcs/GitVcsDriverCore.ts | 20 +++++-------
2 files changed, 21 insertions(+), 32 deletions(-)
diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts
index 0e7e6bba7ec2..fe106c660114 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.test.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts
@@ -828,30 +828,23 @@ it.effect("reads worktrees from git that rejects worktree list -z", () =>
it.effect("does not report a separate git dir as the worktree of its branch", () =>
Effect.scoped(
Effect.gen(function* () {
- const driver = yield* makeGitVcsDriverCore();
+ const driver = yield* GitVcsDriver.GitVcsDriver;
const cwd = yield* makeTmpDir();
- const gitDir = NodeFS.realpathSync(yield* makeTmpDir());
- yield* git(cwd, ["init", `--separate-git-dir=${gitDir}`]).pipe(
- Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
- );
- yield* git(cwd, ["config", "user.email", "test@example.com"]).pipe(
- Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
- );
- yield* git(cwd, ["config", "user.name", "Test"]).pipe(
- Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
- );
- yield* git(cwd, ["commit", "--allow-empty", "-m", "init"]).pipe(
- Effect.provideService(GitVcsDriver.GitVcsDriver, driver),
- );
+ const gitDir = yield* makeTmpDir();
+ yield* git(cwd, ["init", `--separate-git-dir=${gitDir}`]);
+ yield* git(cwd, ["config", "user.email", "test@test.com"]);
+ yield* git(cwd, ["config", "user.name", "Test"]);
+ yield* git(cwd, ["commit", "--allow-empty", "-m", "init"]);
+
const refs = yield* driver.listRefs({ cwd, refresh: true });
+ const worktreePath = refs.refs.find((ref) => ref.current)?.worktreePath;
- assert.isTrue(refs.isRepo);
- const current = refs.refs.find((ref) => ref.current);
- assert.isDefined(current);
- assert.notEqual(current?.worktreePath, gitDir);
- assert.equal(current?.worktreePath, NodeFS.realpathSync(cwd));
+ assert.equal(
+ worktreePath && NodeFS.realpathSync.native(worktreePath),
+ NodeFS.realpathSync.native(cwd),
+ );
}),
- ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
+ ).pipe(Effect.provide(layerTest)),
);
it.effect("refreshes the current branch after an external checkout", () =>
diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts
index addac9dbeb2b..7adc060833f9 100644
--- a/apps/server/src/vcs/GitVcsDriverCore.ts
+++ b/apps/server/src/vcs/GitVcsDriverCore.ts
@@ -3236,17 +3236,17 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
[branchName, path.normalize(path.resolve(worktreePath))] as const,
)
: [];
- // With a separate git dir lacking `core.worktree`, git lists the git dir itself as the
- // main worktree. It is not a checkout, so it must never be offered as one.
+ // Without `core.worktree`, Git reports a separate git dir as the main checkout.
const normalizedGitCommonDir = path.normalize(path.resolve(gitCommonDir));
const existingWorktreeEntries = yield* Effect.filter(
parsedWorktreeEntries,
([, worktreePath]) =>
- worktreePath !== normalizedGitCommonDir &&
- fileSystem.stat(worktreePath).pipe(
- Effect.as(true),
- Effect.orElseSucceed(() => false),
- ),
+ worktreePath === normalizedGitCommonDir
+ ? Effect.succeed(false)
+ : fileSystem.stat(worktreePath).pipe(
+ Effect.as(true),
+ Effect.orElseSucceed(() => false),
+ ),
{ concurrency: 16 },
);
const worktreeMap = new Map(existingWorktreeEntries);
@@ -3437,13 +3437,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
snapshot.localBranches.some((ref) => ref.worktreePath === repositoryPaths.worktreeRoot);
const localBranches = snapshot.localBranches.map((ref) => ({
...ref,
- // A separate git dir is filtered out of the worktree list, which leaves the
- // current branch without a path; fall back to the folder the user opened.
worktreePath:
ref.worktreePath ??
- (!ref.isRemote && ref.name === repositoryPaths.currentBranch
- ? repositoryPaths.worktreeRoot
- : null),
+ (ref.name === repositoryPaths.currentBranch ? repositoryPaths.worktreeRoot : null),
current: hasCurrentWorktreeBranch
? ref.worktreePath === repositoryPaths.worktreeRoot
: ref.name === repositoryPaths.currentBranch,