diff --git a/src/db/connection.ts b/src/db/connection.ts index cf797ff78..f5f88637c 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -292,6 +292,14 @@ export function findDbPath(customPath?: string): string { debug(`findDbPath: stopped at git ceiling ${ceiling}`); break; } + // Outside a git repo, cwd is the first (and only) directory we'll check. + // Walking past it risks attaching to a stale .codegraph/ in an unrelated + // parent — e.g. /private/tmp/.codegraph/ leaking into every /tmp/foo/ run, + // or $HOME/.codegraph/ leaking into every scratch dir under $HOME. + if (!ceiling) { + debug(`findDbPath: no git ceiling, stopping at ${dir}`); + break; + } const parent = path.dirname(dir); if (parent === dir) break; dir = parent; diff --git a/tests/unit/db.test.ts b/tests/unit/db.test.ts index 1b635d5cb..2b7c933da 100644 --- a/tests/unit/db.test.ts +++ b/tests/unit/db.test.ts @@ -350,6 +350,33 @@ describe('findDbPath with git ceiling', () => { fs.rmSync(emptyDir, { recursive: true, force: true }); } }); + + it('does not pick up stale parent .codegraph/ when no git repo exists', () => { + // Regression for the Phase 0 footgun (dogfood report 10.6): when no git + // repo wraps cwd, findDbPath used to walk all the way to `/`, latching + // onto stale .codegraph/ from unrelated parents (e.g. /private/tmp/). + const parentDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-stale-parent-')); + fs.mkdirSync(path.join(parentDir, '.codegraph'), { recursive: true }); + fs.writeFileSync(path.join(parentDir, '.codegraph', 'graph.db'), ''); + const innerDir = path.join(parentDir, 'inner'); + fs.mkdirSync(innerDir, { recursive: true }); + const origCwd = process.cwd; + process.cwd = () => innerDir; + _resetRepoRootCache(); + execFileSyncSpy.mockImplementationOnce(() => { + throw new Error('not a git repo'); + }); + try { + const result = findDbPath(); + // Must NOT return the stale parent's DB. + expect(result).not.toBe(path.join(parentDir, '.codegraph', 'graph.db')); + // Falls back to the default path at cwd. + expect(result).toBe(path.join(innerDir, '.codegraph', 'graph.db')); + } finally { + process.cwd = origCwd; + fs.rmSync(parentDir, { recursive: true, force: true }); + } + }); }); describe('openReadonlyOrFail', () => {