When the indexed root is a directory an enclosing git repo ignores,
`git ls-files --directory` collapses the whole cwd to a single literal
`./` entry. That sentinel reached the `ignore` matcher, which rejects it
("path should be a `path.relative()`d string, but got "./""), aborting
buildScopeIgnore — the one ignore-building call in FileWatcher.start().
So the MCP daemon's startWatching() threw, was caught as "Failed to open
project", and auto-sync never started: the index silently went stale
until a manual `codegraph sync` (CODEGRAPH_NO_DAEMON=1 was the only
workaround).
Filter the `./`/`.` self-entry wherever we consume `--directory` output
(listIgnoredDirs + the untracked-dir loop in discoverEmbeddedRepoRoots).
Semantically correct, not just a crash guard: `./` means "the whole cwd",
never a nested repo to recurse into.
Not platform-specific (reported on Codex/Windows, reproduced on macOS):
the trigger is git state, not the OS.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e43ac82cdf
commit
03666584ed
+17
-2
@@ -256,6 +256,21 @@ function defaultsOnlyIgnore(): Ignore {
|
||||
return ignore().add(DEFAULT_IGNORE_PATTERNS);
|
||||
}
|
||||
|
||||
/**
|
||||
* `git ls-files --directory` collapses a wholly-untracked/ignored directory into
|
||||
* one entry — and when the command's own cwd is such a directory (the indexed
|
||||
* root is itself a git-ignored subdir of an enclosing repo), git emits the
|
||||
* literal `./` meaning "this entire directory". That sentinel is not a real
|
||||
* nested path: feeding it to the `ignore` matcher throws ("path should be a
|
||||
* `path.relative()`d string, but got "./""), which used to abort `buildScopeIgnore`
|
||||
* and so break the MCP daemon's watcher/auto-sync on connect; and joining it back
|
||||
* onto `repoDir` would just re-point at the cwd. Drop it wherever we consume
|
||||
* `--directory` output. (#936)
|
||||
*/
|
||||
function isWholeCwdEntry(entry: string): boolean {
|
||||
return entry === './' || entry === '.' || entry === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* List the gitignored DIRECTORIES of a repo (collapsed, trailing-slash form),
|
||||
* relative to `repoDir`. These are invisible to every other `git ls-files` /
|
||||
@@ -270,7 +285,7 @@ function listIgnoredDirs(repoDir: string): string[] {
|
||||
['ls-files', '-z', '-o', '-i', '--exclude-standard', '--directory'],
|
||||
{ cwd: repoDir, encoding: 'utf-8' as const, timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] as ['pipe', 'pipe', 'pipe'], windowsHide: true }
|
||||
);
|
||||
return out.split('\0').filter((e) => e.endsWith('/'));
|
||||
return out.split('\0').filter((e) => e.endsWith('/') && !isWholeCwdEntry(e));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
@@ -434,7 +449,7 @@ export function discoverEmbeddedRepoRoots(rootDir: string): string[] {
|
||||
{ cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
|
||||
);
|
||||
for (const e of o.split('\0')) {
|
||||
if (e.endsWith('/') && !defaults.ignores(e)) {
|
||||
if (e.endsWith('/') && !isWholeCwdEntry(e) && !defaults.ignores(e)) {
|
||||
candidates.push(...findNestedGitRepos(path.join(repoAbs, e), e));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user