You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
IsClonedAsync() returns true for an empty directory inside another repository's working tree, so "clone if not cloned" skips the clone and later verbs run against the outer repo #185
GitRepository.IsClonedAsync() is documented as deciding "whether LocalPath currently holds a git working tree", but it delegates to GitProbes.IsWorkTreeAsync, which runs git rev-parse --is-inside-work-tree:
A launcher enumerates repositories, maps each to a GitRepository with LocalPath = <root>/<RepoName>, and follows the "if not cloned, clone" pattern. If <root> is itself inside a git working tree (for example a dev folder that is a repo, or a monorepo checkout), then:
IsClonedAsync() returns true for the empty <root>/SomeRepo directory, so the clone is skipped. A manual Clone(...) into the same path still succeeds, which shows the path was never a repository.
Status(), Log(), Commit() and the other verbs on that GitRepository then run against the outer repository, with no error.
The reviewer reproduced this against a build of HEAD with git 2.43: IsClonedAsync printed True for an empty directory inside another repo, and cloning into that directory then succeeded.
The repo has already fixed this for Init(). GitInitBuilder.cs:~133 notes that --is-inside-work-tree "wrongly reports AlreadyExisted = true for a plain subdirectory of an existing repository", and #138 / c246cc6 added GitInitBuilder.IsRepositoryRoot. IsClonedAsync still has the upward-walk problem.
Suggested fix
Make IsClonedAsync answer "is LocalPath the root of a working tree". Either:
require rev-parse --show-cdup to exit 0 with empty output, or
reuse the --is-bare-repository --git-dir --show-cdup probe and GitInitBuilder.IsRepositoryRoot.
Leave IGitClient.IsRepositoryAsync / OpenAsync on the "inside" semantics, since their contract really is "inside a working tree".
Acceptance criteria
IsClonedAsync() returns false for an existing directory, empty or not, that is a subdirectory of another repository's working tree.
Priority: High. This only happens when the clone root sits inside another working tree, such as a dev folder that is a repo or a monorepo checkout. When it does, it fails silently in a way that can change data. The clone is skipped, and from then on Status(), Commit() and the other verbs run against the outer repository with no error. So a "clone if missing, then commit" workflow can write commits to the wrong repo.
Activity
matt-edmondson commentedon Oct 7, 2026
Triage
Status(),Commit()and the other verbs run against the outer repository with no error. So a "clone if missing, then commit" workflow can write commits to the wrong repo.GitRepository.IsClonedAsync(GitIntegration/GitRepository.cs:99-110, at 647dbbc) andExecution/GitProbes.cs:37. Confirmed on HEAD:IsClonedCoreAsyncstill delegates toGitProbes.IsWorkTreeAsync, which runsrev-parse --is-inside-work-tree. The fix can reuse the probe already inBuilders/GitInitBuilder.cs:133-168(IsRepositoryRoot, from Init() reports AlreadyExisted = false when re-initializing a submodule, worktree or --separate-git-dir repository, hiding an ignored --initial-branch #138) instead of adding a new one. As the issue says,IGitClient.IsRepositoryAsync/OpenAsyncshould keep the "inside" semantics.LocalPathis a subdirectory), but it is a different code path and a different fix. No open PR covers this. The only open GitIntegration PR is Clear inherited GIT_DIR, GIT_INDEX_FILE and GIT_DIFF_OPTS before running git #184 (clearing inheritedGIT_DIR/GIT_INDEX_FILE), which is unrelated.Generated by Claude Code