fix(hook): resolve canonical repo root + guard read-only FTS ensure#1226
Conversation
…bhigyanpatwari#1224) Two bugs in the Claude Code hook + query layer integration: 1. `findGitNexusDir` (in `gitnexus/hooks/claude/gitnexus-hook.cjs` and `gitnexus-claude-plugin/hooks/gitnexus-hook.js`) walked upward from cwd looking for a non-registry `.gitnexus/`. In linked git worktrees created via `git worktree add`, the canonical repo's `.gitnexus/` never sits above the worktree path, so the walk silently fails and neither augmentation nor staleness notifications fire. Fix: keep the cwd-walk as the fast path, then fall back to `git rev-parse --git-common-dir` to resolve the shared `.git/` directory (which lives inside the canonical repo across all linked worktrees) and walk up from its parent. Returns null cleanly when `git` isn't on PATH or cwd isn't inside any working tree. 2. `ensureFTSIndex` in the LadybugDB adapter rethrew when the active connection is read-only (e.g. the MCP query pool, which opens DBs read-only by design). Defensive callers used to surface five "Cannot execute write operations in a read-only database" warnings per query. Fix: extract `isReadOnlyDbError` (mirroring the existing `isDbBusyError` discriminator) and have `ensureFTSIndex` catch the read-only error, cache the key, and return silently. Index creation is owned by `gitnexus analyze` on a writable connection — the ensure call is safely a no-op on the read pool. Lock / busy / "already exists" / schema errors continue to propagate. Tests: - `test/unit/hooks.test.ts`: new "Linked git worktree resolution" block exercises both hooks against a real linked worktree to confirm PostToolUse stale notifications fire, plus a negative case when the canonical repo has no `.gitnexus/`. - `test/unit/lbug-readonly-error.test.ts`: new file unit-tests the `isReadOnlyDbError` discriminator (positive matches, case insensitivity, non-Error inputs, and unrelated errors that must still surface — lock contention, "already exists", schema misses). - `test/integration/lbug-core-adapter.test.ts`: extends the existing FTS coverage with an idempotency assertion for `ensureFTSIndex` to pin the read-only guard's success-path contract. Verified with `npx tsc --noEmit` and `vitest run` on the affected files (hooks + readonly + lbug-core-adapter + bm25-search + lbug-extension-loader + lbug-embedding-hashes — 136 tests pass). Build: `npm run build` succeeds. Closes abhigyanpatwari#1224
|
@sburdges-eng is attempting to deploy a commit to the NexusCore Team on Vercel. A member of the Team first needs to authorize it. |
|
Claude finished @magyargergo's task in 4m 10s —— View job PR #1181 Production Readiness Reviewfix(local-backend): skip vector index query on unsupported platforms Tasks
Review bar for this PR
Understanding of the changeIssue #1178: Why the local backend was a gap: The core embedding pipeline already checked What changes on unsupported platforms: What stays unchanged on supported platforms: Out of scope: The underlying LadybugDB native VECTOR crash on unsupported platforms. Extension lifecycle or install policy. No lockfile or dependency changes. Findings[medium] No explicit test asserting
|
| DoD item | Status |
|---|---|
On unsupported platforms, semanticSearch() does not issue CALL QUERY_VECTOR_INDEX(...) |
satisfied — guard prevents entry entirely; test proves no QUERY_VECTOR_INDEX call |
On unsupported platforms, proceeds to real exact-scan CodeEmbedding fallback |
satisfied — bestChunks stays empty; test asserts RETURN e.nodeId AS nodeId + e.embedding AS embedding in issued queries, distinguishing from count query |
| On supported platforms, vector-index behavior unchanged | partially satisfied — code is provably unchanged; test defaults to supported mode; but no explicit assertion that QUERY_VECTOR_INDEX still fires (see finding above) |
Uses same isVectorExtensionSupportedByPlatform() as core embedding code |
satisfied — imported directly from canonical ../../core/platform/capabilities.js; no duplicate policy |
| No new extension-loading or install behavior | satisfied |
| Diagnostics: stderr-only, one-shot per session, safe for MCP JSON-RPC stdout | satisfied — console.error, guarded by warnedVectorUnsupported per-instance flag, mirrors warnedSiblingDrift pattern; stdout is never touched |
| Diagnostics: wording accurate and not misleading | partially satisfied — "unavailable" is imprecise; see low finding above |
| Tests prove actual scan query issued (not only count query) | satisfied — assertion checks for RETURN e.nodeId AS nodeId AND e.embedding AS embedding, which only appear in the scan query, not the count query |
| TypeScript clean; CI passes on 3 platforms | satisfied — PR claims tsc --noEmit clean; CI reported 7,667 tests passed on 3 platforms |
Validation assessment
Tests added / updated:
- New test
'skips vector index query when VECTOR is unsupported by the platform'incalltool-dispatch.test.ts - Mock infrastructure for
isVectorExtensionSupportedByPlatformadded tovi.hoisted()block; module mock properly usesasync (importOriginal)to spread real exports beforeEachresets platform mock totrue— isolation is correctconsole.errorspied and restored infinally✓
Validation claimed by the PR:
npx vitest run --project default "test/unit/platform-capabilities.test.ts"— 2 tests passednpx vitest run --project default "test/unit/calltool-dispatch.test.ts"— 67 tests passednpx tsc --noEmitclean- CI: typecheck + unit tests on 3 platforms (ubuntu, macOS, Windows), 7,667 tests, 2 skipped
Verified from diff / CI evidence:
- Diff is correct and matches the claimed description end-to-end
- The weak assertion concern from earlier review feedback is resolved: the test checks
RETURN e.nodeId AS nodeId+e.embedding AS embedding, which only appear in the scan projection, not the count query - Diagnostic is asserted in the test with
stringContaining(...)✓ - PR fix(local-backend): #1178 skip vector index query on unsupported pla… #1181 was merged as commit
9cd8c36— CI for that SHA is not directly inspectable in the current context, but the PR description is consistent with the diff
Missing / not verifiable:
- No test explicitly asserting
QUERY_VECTOR_INDEXstill fires on supported platforms (see medium finding) - No end-to-end MCP smoke test with a real Windows DB and real embeddings — unit mocks are adequate for this targeted fix, but a real Windows integration test would close the gap entirely
- CI logs for the merged
9cd8c36SHA are not available to verify platform-specific run artifacts
Does missing validation block production readiness? No. The missing supported-platform assertion is a follow-up risk, not a current regression. The unit mock approach is proportionate for a platform guard that wraps unchanged code.
Final verdict
production-ready with minor follow-ups
The core correctness of this PR is sound: the guard correctly prevents CALL QUERY_VECTOR_INDEX(...) on unsupported platforms using the canonical platform capability function, bestChunks is never populated on the unsafe path (ensuring fallback executes), the diagnostic is safely routed to stderr and deduplicated per instance, and the test strengthening over the prior weak assertion is real — the scan query assertion specifically targets RETURN e.nodeId AS nodeId and e.embedding AS embedding, which only appear in the full scan, not the count query. The two follow-ups — adding a supported-platform regression test and tightening the diagnostic wording — are low-risk cleanups that don't block the crash-avoidance goal of this fix. The change is small, correctly scoped, easy to revert, and consistent with platform capability policy across the codebase.
|
@sburdges-eng thank you for your contribution and nice work! Could you please tackle with the findings in this PR? 🙏 |
CI Report✅ All checks passed Pipeline Status
Test Results
✅ All 7718 tests passed 1 test(s) skipped — expand for details
Code CoverageTests
📋 View full run · Generated by CI |
Add the supported-platform regression assertion for QUERY_VECTOR_INDEX and align the unsupported VECTOR diagnostic wording with platform policy. Made-with: Cursor
Closes #1224
This is my first contribution — thanks for building GitNexus. Two small, focused fixes for the Claude Code hook + query layer integration. Happy to iterate on style, scope, or naming.
Summary
Bug 1 —
findGitNexusDirsilently fails in linked git worktreesThe PreToolUse / PostToolUse hook (in both
gitnexus/hooks/claude/gitnexus-hook.cjsandgitnexus-claude-plugin/hooks/gitnexus-hook.js) walks the filesystem upward from cwd looking for a non-registry.gitnexus/. In a linked worktree created viagit worktree add ../<repo>-worktrees/feature-x, the canonical repo's.gitnexus/never sits above the worktree path, so the walk silently fails — neither pattern augmentation nor staleness notifications fire.Fix: Keep the cwd-walk as the fast path, then fall back to
git rev-parse --git-common-dir. The--git-common-diroutput is the shared.git/directory across all linked worktrees (it always lives inside the canonical repo root), so its parent is the canonical repo. Returns null cleanly whengitisn't on PATH or cwd isn't inside any working tree, preserving the existing graceful-failure contract.Bug 2 — FTS index ensure fails on read-only DB connection
The MCP query pool opens LadybugDB read-only (correct by design — multiple processes read concurrently and
analyzeholds the write lock). Defensive callers ofensureFTSIndexfrom that pool surfaced fiveCannot execute write operations in a read-only databasewarnings per call (one each forFile/Function/Class/Method/Interface).Fix (lower-risk option): Add the read-only error to
ensureFTSIndex's catch list. Index creation is owned bygitnexus analyzeon a writable connection, so the ensure call is safely a no-op on the read pool. The fix mirrors the existingisDbBusyErrordiscriminator pattern with a newisReadOnlyDbError, keeping the lock / busy / "already exists" / schema-error paths intact. After this PR there is no stderr noise on this path.Note: I checked that the current query path on
mainno longer callsensureFTSIndexfrom a read-only connection (searchFTSFromLbugonly invokesqueryFTS/queryFTSViaExecutor, andpool-adapteronly loads the FTS extension). The reproduction comes fromgitnexus@1.6.3(the published npm package), which still has the older path. This PR adds defense-in-depth so any future call site — or a re-introduction during refactoring — can't regress to the noisy behavior.Test plan
gitnexus/test/unit/hooks.test.ts— new "Linked git worktree resolution" describe block runs against both hook variants. Creates a realgit worktree addlinked tree and asserts PostToolUse fires the stale notification when the canonical repo has.gitnexus/, and stays silent when it doesn't.gitnexus/test/unit/lbug-readonly-error.test.ts— new file unit-testsisReadOnlyDbError(canonical message, wrapped prefix, case-insensitivity, non-Error inputs, plus negative cases for lock contention, "already exists", missing tables, and generic transient errors that must still surface).gitnexus/test/integration/lbug-core-adapter.test.ts— extended FTS coverage with an idempotency assertion forensureFTSIndexto pin the read-only guard's success-path contract.npx tsc --noEmitclean.vitest runon the affected files (hooks + readonly + lbug-core-adapter + bm25-search + lbug-extension-loader + lbug-embedding-hashes — 136 tests pass).npm run buildsucceeds end-to-end.prettier --checkclean on touched files;eslintadds no new errors (one new non-null-assertion warning that matches the existing test file's pattern).Out of scope / observations
gitnexus-claude-plugin/hooks/gitnexus-hook.jsandgitnexus/hooks/claude/gitnexus-hook.cjsfiles share substantial logic and drift apart. Not in this PR's scope, but a future refactor to a single source-of-truth (with a small build step that emits the.cjsvariant) would prevent the next "fix one, miss the other" issue.git rev-parseapproach rather than the~/.gitnexus/registry.jsonlookup since the existing hook already shells out togitfor HEAD checks — this keeps it consistent and avoids a new file-format dependency in the hook. Happy to switch approach if you'd prefer the registry path.