fix(server): reject a bare repository root when adding a project - #8646
fix(server): reject a bare repository root when adding a project#8646walid-baharwal wants to merge 1 commit into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
| const workspacePaths = yield* WorkspacePaths.WorkspacePaths; | ||
| const workspaceRoot = yield* normalizeWorkspaceRootForProjectCommand( | ||
| flags.workspaceRoot, | ||
| ).pipe(Effect.flatMap(workspacePaths.ensureNotBareRepositoryLayout)); |
There was a problem hiding this comment.
🟠 High cli/project.ts:467
project add rejects a valid empty git init --separate-git-dir working tree because ensureNotBareRepositoryLayout classifies any in-root gitdir without an index as bare. Conversely, adding an index to the embedded gitdir makes the same bare/worktree-only layout pass, so this heuristic both rejects valid repositories and permits the cross-worktree scope it is meant to prevent. Please detect the repository layout using Git metadata rather than index presence.
Also found in 2 other location(s)
apps/server/src/workspace/WorkspacePaths.ts:209
isBareRepositoryLayouttreats any in-root gitdir without anindexas bare. A freshly initialized working tree made withgit init --separate-git-dir <root>/storehas exactly the.gitpointer created bygit initbut no index untilgit addstages a file, so adding that valid empty project is rejected as a bare-worktree layout.
apps/server/src/workspace/WorkspacePaths.ts:209
The missing-index heuristic also has a false-negative path:
isBareRepositoryLayoutreturnsfalsewhenever the embedded bare git directory contains anindex. An index is not a property of a working tree (Git's index tooling can write$GIT_DIR/indexwithout a worktree), so a bare-root/worktree layout with an existing index is accepted and recreates the cross-worktree project scope this check is intended to prevent.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/cli/project.ts around line 467:
`project add` rejects a valid empty `git init --separate-git-dir` working tree because `ensureNotBareRepositoryLayout` classifies any in-root gitdir without an `index` as bare. Conversely, adding an `index` to the embedded gitdir makes the same bare/worktree-only layout pass, so this heuristic both rejects valid repositories and permits the cross-worktree scope it is meant to prevent. Please detect the repository layout using Git metadata rather than index presence.
Also found in 2 other location(s):
- apps/server/src/workspace/WorkspacePaths.ts:209 -- `isBareRepositoryLayout` treats any in-root gitdir without an `index` as bare. A freshly initialized working tree made with `git init --separate-git-dir <root>/store` has exactly the `.git` pointer created by `git init` but no index until `git add` stages a file, so adding that valid empty project is rejected as a bare-worktree layout.
- apps/server/src/workspace/WorkspacePaths.ts:209 -- The missing-index heuristic also has a false-negative path: `isBareRepositoryLayout` returns `false` whenever the embedded bare git directory contains an `index`. An index is not a property of a working tree (Git's index tooling can write `$GIT_DIR/index` without a worktree), so a bare-root/worktree layout with an existing index is accepted and recreates the cross-worktree project scope this check is intended to prevent.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9ecd6ca. Configure here.
A worktree-only layout keeps a bare repository inside the root (commonly `<root>/.bare`) and checks every branch out as a sibling directory, so the root holds no working tree of its own. `git rev-parse --is-inside-work-tree` still answers true there, so the root was accepted as an ordinary repository and became the parent of every worktree: status reported each worktree as untracked, the diff view was scoped across all of them, and a thread's cwd spanned every checkout at once. Recognize the layout where a project is added and explain that a worktree directory inside it is what to add. Detection reads the `.git` file: a directory `.git` is an ordinary repository, and a `.git` file pointing outside the root is a linked worktree or a submodule. What remains is shared with `git init --separate-git-dir`, and git records nothing that separates the two — it writes no `core.worktree` for either — so two structural signals are required together: the git directory hosts linked worktrees, and it never staged anything of its own. A working tree that has committed has an index; a bare repository does not. Anything ambiguous is accepted, since wrongly refusing a valid root is worse than the misscoping this prevents. Containment is tested with a relative path so a root at the filesystem boundary is compared correctly. Fixes pingdotgg#8164
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR changes project creation behavior across CLI and server orchestration using a nontrivial filesystem heuristic to classify bare-repository layouts. The gate affects existing project-creation paths, and reported edge cases around valid layouts warrant human review. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
9ecd6ca to
48fc1cd
Compare

What Changed
Adding a project whose root holds a bare repository and its worktrees — no working tree of
its own — is now rejected with an explanation, instead of being accepted as an ordinary
repository.
Verified through the real CLI against layouts built with real git:
Before this change the first command printed
Added project … at <root>, andgit statusthere reported every worktree as untracked (
?? .bare/,?? develop/).Why
Fixes #8164.
git rev-parse --is-inside-work-treeanswers true at such a root oncecore.bareis false, so the root was accepted and became the parent of every worktree: thediff view scoped across all of them and a thread's cwd spanned every checkout at once.
Detection reads the
.gitfile only — no new git invocation. A directory.gitis anordinary repository; a
.gitfile pointing outside the root is a linked worktree or asubmodule. Among what is left, the missing index is the discriminator: a bare repository
stages nothing, while
git init --separate-git-dirpointed inside its own working treewrites one. Six real layouts were checked end to end through
t3 project add:git init --separate-git-dirinside the root, with a worktreeThe gitdir parse moved into
packages/shared/src/git.tsand is now shared withdevHome.ts, which had the same parse inline; that also gives this path the Windowsbackslash normalization it was missing.
Two gaps left uncovered on purpose:
serverRuntimeStartup.ts) dispatchesproject.createdirectly, solaunching with
--cwd <bare root>still creates the project. Rejecting there would failserver startup rather than inform anyone, which is worse than the bug.
orchestration/http.tsmaps dispatch failures toinvalid_command, dropping themessage for HTTP callers. Pre-existing and not this change's to fix.
UI Changes
None rendered. The rejection reuses the existing "Failed to add project" toast in web and
the error banner in mobile — both already display
error.message, so no client change wasneeded. CLI output matches the shape of the existing workspace-root errors.
Verification
Plus the real-git matrix above, driven through
t3 project addwith a scratchT3CODE_HOME. The rejection test fails without the source change.Checklist
documented above as uncovered
Model: Claude Opus 5 (1M context). Harness: Claude Code.
Note
Medium Risk
Changes which paths can become projects using filesystem heuristics; misclassification could block valid roots or still allow mis-scoped ones, but scope is limited to project-add/create paths.
Overview
Adding a project now rejects workspace roots that are only a bare-repo + worktree container (no checkout of their own), with
WorkspaceRootBareRepositoryLayoutErrortelling users to pick a worktree directory instead.Detection lives on
WorkspacePaths.ensureNotBareRepositoryLayout: after normalizing the path, it reads the root’s.gitfile (no extragitcalls), resolves an in-root gitdir, and treats the layout as invalid when that gitdir has linked worktrees and noindex—while still allowing ordinary repos, linked worktrees, separate-git-dir setups, and bare roots before any worktree exists (per tests).The check runs on
t3 project addand on orchestrationproject.createnormalization; other callers ofnormalizeWorkspaceRootare unchanged.parseGitDirPointeris extracted topackages/shared/src/git.ts(with Windows backslash normalization) and reused fromdevHomefor linked-worktree detection.Reviewed by Cursor Bugbot for commit 48fc1cd. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Reject bare repository root layout in
projectAddCommandandnormalizeProjectWorkspaceRootForCreateWorkspacePaths.ensureNotBareRepositoryLayout, which detects a bare-repository layout:.gitis a file pointing to a gitdir inside the root,gitdir/worktreeshas entries, andgitdirhas no index file. Fails withWorkspaceRootBareRepositoryLayoutErrorwhen detected.normalizeWorkspaceRootin both the project-add CLI handler and theproject.createdispatch normalization path, so only roots with an actual working tree are accepted.parseGitDirPointerto git.ts and refactors pointsAtLinkedWorktree to use it for robust.gitfile parsing.WorkspacePathscallers intentionally do not enforce this check.Macroscope summarized 48fc1cd.