Skip to content

fix: remove FTS5 dependency to fix SqlClient Service not found at startup#4

Merged
Josh-wt merged 1 commit intomainfrom
claude/understand-codebase-9Fmym
Apr 11, 2026
Merged

fix: remove FTS5 dependency to fix SqlClient Service not found at startup#4
Josh-wt merged 1 commit intomainfrom
claude/understand-codebase-9Fmym

Conversation

@Josh-wt
Copy link
Copy Markdown
Owner

@Josh-wt Josh-wt commented Apr 11, 2026

Summary

Fixes the Service not found: effect/sql/SqlClient runtime error reported when running bun dev:server.

Root cause: Migration 020 created a memory_fts FTS5 virtual table and three associated triggers. SQLite distributions compiled without FTS5 (Node.js built-in SQLite, some Bun builds) fail this statement. The migration runs inside Layer.provideMerge(setup, makeRuntimeSqliteLayer(...)) — a failure in setup prevents the entire combined layer from registering, so SqlClient is never put into the Effect service context. Every downstream service then reports the misleading Service not found: effect/sql/SqlClient.

Changes:

  • 020_NewFeatureTables.ts — remove the CREATE VIRTUAL TABLE memory_fts USING fts5(...) and the three insert/delete/update triggers. The memory_entries table is untouched.
  • ProjectMemoryService.ts — replace the FTS5 MATCH query in search with LIKE predicates on title, content, and tags columns, ordered by relevance_score. Remove the FTS5 index rebuild from index (forceReindex becomes a no-op).

Test plan

  • bun typecheck passes (7/7 packages)
  • bun lint — 0 errors
  • bun fmt --check — all files correctly formatted
  • Start server with bun dev:server — no Service not found: SqlClient error on startup

https://claude.ai/code/session_01Nxa3JfS5jZVHsnJmaaHvbW


Summary by cubic

Removes the FTS5 dependency and switches memory search to simple LIKE queries, fixing the Service not found: effect/sql/SqlClient error on bun dev:server startup.

  • Bug Fixes
    • Removed the memory_fts FTS5 virtual table and related triggers from 020_NewFeatureTables.ts to avoid migration failures on SQLite builds without FTS5.
    • Replaced FTS5 MATCH in ProjectMemoryService.search with LIKE on title, content, and tags, ordered by relevance_score.
    • Dropped FTS index rebuild; forceReindex is now a no-op.

Written for commit 2f40ece. Summary will update on new commits.

Summary by CodeRabbit

  • Refactor
    • Simplified memory search implementation for improved maintainability
    • Optimized access tracking mechanism in the project memory service
    • Streamlined memory indexing system

…rtup

Migration 020 created a memory_fts FTS5 virtual table and three triggers.
SQLite distributions without FTS5 support (Node.js built-in SQLite, some
Bun builds) fail the migration mid-way. Because the migration runs inside
the Effect setup layer that is composed with Layer.provideMerge alongside
makeRuntimeSqliteLayer, a migration failure causes the entire persistence
layer construction to fail — SqlClient is never registered in the Effect
context, so every downstream service reports the misleading error:

  Service not found: effect/sql/SqlClient

Fix:
- Drop the memory_fts virtual table, insert/delete/update triggers from
  migration 020 (no schema change is needed for already-run migrations
  since the tables were in the same transaction and will re-run cleanly
  on a fresh DB, and users who ran it successfully already have those
  tables harmlessly present)
- Replace the FTS5 MATCH query in ProjectMemoryService.search with
  three LIKE predicates across title, content, and tags columns
- Remove the FTS5 index rebuild from ProjectMemoryService.index
  (forceReindex becomes a no-op, which is fine for LIKE search)

bun typecheck passes (7/7 packages).

https://claude.ai/code/session_01Nxa3JfS5jZVHsnJmaaHvbW
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

The changes migrate from Full-Text Search (FTS5) implementation to a LIKE-based search pattern. The service replaces FTS5 querying with wildcard pattern matching on title, content, and tags fields, and removes the FTS virtual table and its three synchronization triggers from the database schema.

Changes

Cohort / File(s) Summary
Search Implementation Refactor
apps/server/src/memory/Services/ProjectMemoryService.ts
Replaced FTS5 MATCH queries with LIKE-based pattern matching (%${input.query}%); updated result mapping from FTS rank to relevance_score; refactored access-count parameter handling from sql.unsafe with manual placeholders to sql.in() parameterization; removed FTS index rebuild logic from index method.
Database Schema Migration
apps/server/src/persistence/Migrations/020_NewFeatureTables.ts
Removed FTS5 virtual table memory_fts creation and three associated triggers (AFTER INSERT, AFTER DELETE, AFTER UPDATE) that kept FTS synchronized with memory_entries.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 From FTS ranks to LIKE we go,
Triggers fade like morning snow,
Pattern matching, simple and free,
A lighter search for you and me,
Rabbit's hop skips merrily! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly describes the main change: removing FTS5 dependency to resolve a startup error, which directly matches the core objective and changes in the PR.
Description check ✅ Passed The description is comprehensive and follows the template structure with clear 'What Changed' and 'Why' sections, root cause explanation, detailed change list, and test plan. It provides sufficient context for understanding the fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/understand-codebase-9Fmym

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f40ece2ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

LIMIT ?`,
queryParams,
);
const pattern = `%${input.query}%`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape LIKE wildcards in memory search query

Building pattern as %${input.query}% means % and _ in user input are treated as SQL wildcards, not literal characters. In this memory feature, queries often include code identifiers (for example user_id or % in snippets), so searches can return unrelated entries and evict relevant matches under LIMIT. Please escape wildcard characters (and use ESCAPE) before binding the pattern so literal queries behave predictably.

Useful? React with 👍 / 👎.

END
`);

// ── Presence / Session Sharing ─────────────────────────────────────
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add forward migration to remove legacy FTS artifacts

This commit removes FTS DDL by editing migration 020 in place, which only affects fresh databases. Existing databases that already ran the previous 020 will keep memory_fts and its triggers indefinitely, but search no longer uses them; those triggers still execute on every memory_entries write and can preserve FTS5 schema dependencies in upgraded installs. A new migration should explicitly drop memory_fts and its triggers so upgraded environments converge to the new schema.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/memory/Services/ProjectMemoryService.ts (1)

175-182: ⚠️ Potential issue | 🟠 Major

Don’t silently treat forceReindex as success.

memoryIndex is still callable from apps/server/src/ws.ts Lines 1071-1073, so callers can request a rebuild and get a success-shaped response even though nothing happened. Also, the migration snippet in apps/server/src/persistence/Migrations/020_NewFeatureTables.ts Lines 201-214 only stops creating FTS objects on fresh databases; it does not remove legacy memory_fts tables/triggers from upgraded ones. Please either add a forward migration to drop the old FTS artifacts and reject forceReindex with a domain-specific error, or remove that flag from the contract.

As per coding guidelines, "Define domain-specific error types (e.g., ProviderAdapterError, OrchestrationDispatchError) in the server for proper error handling".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/memory/Services/ProjectMemoryService.ts` around lines 175 -
182, The index implementation currently treats the forceReindex flag as a no-op
and returns a success-shaped response; update ProjectMemoryService.ts (the index
function) to explicitly reject calls that set input.forceReindex by
throwing/returning a new domain-specific error (e.g.,
MemoryReindexNotSupportedError) so callers like memoryIndex cannot assume a
rebuild occurred, and add a forward migration (referencing 020_NewFeatureTables
migration and the legacy memory_fts table/triggers) to drop legacy FTS artifacts
on upgraded databases; ensure the new error type follows the server's
domain-error pattern (similar to ProviderAdapterError) and is used in ws.ts call
sites to surface a clear error instead of a silent success.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/memory/Services/ProjectMemoryService.ts`:
- Around line 59-99: Escape SQL LIKE wildcards in the user query before building
the pattern: sanitize input.query by first replacing backslashes with double
backslashes, then replacing '%' with '\%' and '_' with '\_' (e.g., const escaped
= input.query.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
const pattern = `%${escaped}%`), and add an explicit ESCAPE '\' clause to both
SQL statements that use pattern (the two sql`... WHERE ... (title LIKE
${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern}) ...` branches in
ProjectMemoryService.ts) so the backslash escapes are honored.

---

Outside diff comments:
In `@apps/server/src/memory/Services/ProjectMemoryService.ts`:
- Around line 175-182: The index implementation currently treats the
forceReindex flag as a no-op and returns a success-shaped response; update
ProjectMemoryService.ts (the index function) to explicitly reject calls that set
input.forceReindex by throwing/returning a new domain-specific error (e.g.,
MemoryReindexNotSupportedError) so callers like memoryIndex cannot assume a
rebuild occurred, and add a forward migration (referencing 020_NewFeatureTables
migration and the legacy memory_fts table/triggers) to drop legacy FTS artifacts
on upgraded databases; ensure the new error type follows the server's
domain-error pattern (similar to ProviderAdapterError) and is used in ws.ts call
sites to surface a clear error instead of a silent success.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e64ae1f2-2a75-49ba-b482-7e431ba9ec8f

📥 Commits

Reviewing files that changed from the base of the PR and between fe024b0 and 2f40ece.

📒 Files selected for processing (2)
  • apps/server/src/memory/Services/ProjectMemoryService.ts
  • apps/server/src/persistence/Migrations/020_NewFeatureTables.ts
💤 Files with no reviewable changes (1)
  • apps/server/src/persistence/Migrations/020_NewFeatureTables.ts

Comment on lines +59 to +99
const pattern = `%${input.query}%`;
const rows = yield* (input.kind
? sql<{
id: string;
project_id: string;
thread_id: string | null;
kind: string;
title: string;
content: string;
tags: string;
relevance_score: number;
access_count: number;
created_at: string;
updated_at: string;
expires_at: string | null;
}>`SELECT * FROM memory_entries
WHERE project_id = ${input.projectId}
AND kind = ${input.kind}
AND (title LIKE ${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern})
AND (expires_at IS NULL OR expires_at > datetime('now'))
ORDER BY relevance_score DESC
LIMIT ${input.limit}`
: sql<{
id: string;
project_id: string;
thread_id: string | null;
kind: string;
title: string;
content: string;
tags: string;
relevance_score: number;
access_count: number;
created_at: string;
updated_at: string;
expires_at: string | null;
}>`SELECT * FROM memory_entries
WHERE project_id = ${input.projectId}
AND (title LIKE ${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern})
AND (expires_at IS NULL OR expires_at > datetime('now'))
ORDER BY relevance_score DESC
LIMIT ${input.limit}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape LIKE wildcards in the user query.

input.query is now fed straight into %...%, so % and _ inside real queries become SQL wildcards. That breaks common searches like snake_case and can turn % into “match everything”.

Suggested fix
-      const pattern = `%${input.query}%`;
+      const escapedQuery = input.query.replace(/[\\%_]/g, "\\$&");
+      const pattern = `%${escapedQuery}%`;
@@
-               AND (title LIKE ${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern})
+               AND (
+                 title LIKE ${pattern} ESCAPE '\\'
+                 OR content LIKE ${pattern} ESCAPE '\\'
+                 OR tags LIKE ${pattern} ESCAPE '\\'
+               )
@@
-               AND (title LIKE ${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern})
+               AND (
+                 title LIKE ${pattern} ESCAPE '\\'
+                 OR content LIKE ${pattern} ESCAPE '\\'
+                 OR tags LIKE ${pattern} ESCAPE '\\'
+               )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pattern = `%${input.query}%`;
const rows = yield* (input.kind
? sql<{
id: string;
project_id: string;
thread_id: string | null;
kind: string;
title: string;
content: string;
tags: string;
relevance_score: number;
access_count: number;
created_at: string;
updated_at: string;
expires_at: string | null;
}>`SELECT * FROM memory_entries
WHERE project_id = ${input.projectId}
AND kind = ${input.kind}
AND (title LIKE ${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern})
AND (expires_at IS NULL OR expires_at > datetime('now'))
ORDER BY relevance_score DESC
LIMIT ${input.limit}`
: sql<{
id: string;
project_id: string;
thread_id: string | null;
kind: string;
title: string;
content: string;
tags: string;
relevance_score: number;
access_count: number;
created_at: string;
updated_at: string;
expires_at: string | null;
}>`SELECT * FROM memory_entries
WHERE project_id = ${input.projectId}
AND (title LIKE ${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern})
AND (expires_at IS NULL OR expires_at > datetime('now'))
ORDER BY relevance_score DESC
LIMIT ${input.limit}`);
const escapedQuery = input.query.replace(/[\\%_]/g, "\\$&");
const pattern = `%${escapedQuery}%`;
const rows = yield* (input.kind
? sql<{
id: string;
project_id: string;
thread_id: string | null;
kind: string;
title: string;
content: string;
tags: string;
relevance_score: number;
access_count: number;
created_at: string;
updated_at: string;
expires_at: string | null;
}>`SELECT * FROM memory_entries
WHERE project_id = ${input.projectId}
AND kind = ${input.kind}
AND (
title LIKE ${pattern} ESCAPE '\\'
OR content LIKE ${pattern} ESCAPE '\\'
OR tags LIKE ${pattern} ESCAPE '\\'
)
AND (expires_at IS NULL OR expires_at > datetime('now'))
ORDER BY relevance_score DESC
LIMIT ${input.limit}`
: sql<{
id: string;
project_id: string;
thread_id: string | null;
kind: string;
title: string;
content: string;
tags: string;
relevance_score: number;
access_count: number;
created_at: string;
updated_at: string;
expires_at: string | null;
}>`SELECT * FROM memory_entries
WHERE project_id = ${input.projectId}
AND (
title LIKE ${pattern} ESCAPE '\\'
OR content LIKE ${pattern} ESCAPE '\\'
OR tags LIKE ${pattern} ESCAPE '\\'
)
AND (expires_at IS NULL OR expires_at > datetime('now'))
ORDER BY relevance_score DESC
LIMIT ${input.limit}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/memory/Services/ProjectMemoryService.ts` around lines 59 -
99, Escape SQL LIKE wildcards in the user query before building the pattern:
sanitize input.query by first replacing backslashes with double backslashes,
then replacing '%' with '\%' and '_' with '\_' (e.g., const escaped =
input.query.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
const pattern = `%${escaped}%`), and add an explicit ESCAPE '\' clause to both
SQL statements that use pattern (the two sql`... WHERE ... (title LIKE
${pattern} OR content LIKE ${pattern} OR tags LIKE ${pattern}) ...` branches in
ProjectMemoryService.ts) so the backslash escapes are honored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants