Skip to content

fix(rag): roll back + surface a document that fails to embed (no silent dead KB entries)#452

Closed
alichherawalla wants to merge 1 commit into
mainfrom
fix/kb-doc-indexing-honesty
Closed

fix(rag): roll back + surface a document that fails to embed (no silent dead KB entries)#452
alichherawalla wants to merge 1 commit into
mainfrom
fix/kb-doc-indexing-honesty

Conversation

@alichherawalla

@alichherawalla alichherawalla commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

indexDocument caught embedding failure non-fatally and still returned success. A document with zero embeddings then showed as "added" in the Knowledge Base but was not searchable (semantic search skips it) — the "looks indexed but doesn't actually work" lie. backfillEmbeddings has no auto-trigger, so the swallowed failure stranded the doc permanently.

Fix

On any embedding failure (load throw, embedBatch throw/OOM, or an embedding-count vs chunk-count mismatch), roll back the just-inserted document + chunks and throw. KnowledgeBaseScreen already alerts on an index throw, so the user sees the real error and can retry — never a silent non-functional entry.

Tests (jest, fails-before / passes-after)

Replaced the old continues without embeddings if embedding fails test (which encoded the lie) with three cases: load failure, embedBatch failure (OOM), and embedding-count mismatch — each asserts indexDocument throws and rolls back (deleteDocument called), with insertEmbeddingsBatch never called.

Provit (on-device E2E)

Pending — a KB round-trip journey (index a real doc → confirm searchable; feed a doc that fails to embed → confirm the error surfaces, no silent dead entry). jest mocks the sqlite/native boundary, so the true round-trip proof is on-device. Self-audit comment below.

Scope

Fixes the "not actually working" half of the KB honesty issue. The truncation-honesty half (>500KB silently cut) is a separate follow-up PR.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved document indexing reliability by rolling back failed indexing attempts instead of leaving partially indexed documents.
    • Prevented documents from appearing indexed when required search embeddings were not created.
    • Added validation to ensure the number of generated embeddings matches the number of content chunks before saving them.

…nt dead entries)

indexDocument caught embedding failure non-fatally and still returned success, so a
document with ZERO embeddings showed as 'added' in the Knowledge Base but was not
searchable (semantic search skips it) — 'looks indexed but doesn't actually work'.
backfillEmbeddings has no auto-trigger, so the swallowed failure stranded the doc
permanently.

Now: on any embedding failure (load throw, embedBatch throw/OOM, or an embedding-count
mismatch), roll back the just-inserted document + chunks and throw. KnowledgeBaseScreen
already alerts on an index throw, so the user sees the real error and can retry instead
of a silent non-functional entry.

Tests: replace the 'continues without embeddings' test (which encoded the lie) with
three fails-before/passes-after cases — load failure, embedBatch failure, and
embedding-count mismatch all roll back (deleteDocument called) and throw, with
insertEmbeddingsBatch never called.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

RagService.indexDocument now validates that the number of generated embeddings matches chunk count and, on any embedding-related failure or mismatch, deletes the just-inserted document and rethrows an error instead of silently continuing. Unit tests updated to verify rollback behavior in these failure scenarios.

Changes

Embedding Rollback in RagService

Layer / File(s) Summary
Embedding validation and rollback logic
src/services/rag/index.ts
Adds a check that embeddings length matches chunk row IDs, throwing if mismatched; replaces prior non-fatal error logging on embedding failure with document deletion rollback, failure logging, and rethrown error.
Rollback test coverage
__tests__/unit/services/rag/index.test.ts
Replaces the test allowing indexing to continue without embeddings with three tests verifying deleteDocument is called and insertEmbeddingsBatch is not called on load failure, embedBatch rejection, and embedding/chunk count mismatch.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant RagService
  participant EmbeddingService
  participant RagDatabase
  Caller->>RagService: indexDocument(doc)
  RagService->>RagDatabase: insert document
  RagService->>EmbeddingService: embedBatch(chunks)
  EmbeddingService-->>RagService: embeddings or error
  alt embedding failure or count mismatch
    RagService->>RagDatabase: deleteDocument(docId)
    RagService-->>Caller: throw indexing error
  else success
    RagService->>RagDatabase: insertEmbeddingsBatch(embeddings)
    RagService-->>Caller: success
  end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the fix and tests, but it misses required template sections like Summary, Type of Change, Checklist, and Related Issues. Add the required template sections: Summary, Type of Change, Checklist, Related Issues, and Additional Notes; mark screenshots as not applicable if this is non-UI.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and matches the main change: rollback and surfacing embedding failures in RagService.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 fix/kb-doc-indexing-honesty

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

@alichherawalla

Copy link
Copy Markdown
Collaborator Author

Self-audit

SOLID / abstraction

  • Enough to abstract? No new abstraction needed — the fix stays in the existing owning seam (RagService.indexDocument), which already owns the ingest lifecycle. No caller branches on a concrete type or Platform.OS.
  • SRP / DIP: One responsibility (ingest = doc is only "indexed" if it's actually embedded/searchable). Callers (KnowledgeBaseScreen) depend on the service throwing, not on any internal detail — they already handle the throw via an existing alert.
  • Single source of truth: The invariant "no embeddings ⇒ not a valid indexed doc" now lives in exactly one place (indexDocument's catch), instead of being silently violated.
  • Verdict: clean. (Deliberately did NOT wire an auto-backfill retry — that's a larger change; backfillEmbeddings is currently dead code, so throw+rollback is the honest minimal fix.)

Tests — no false green

  • Unit: drives the REAL RagService.indexDocument orchestration (the try/catch → rollback → throw logic runs for real).
  • Mocks: only genuine boundaries — ragDatabase (op-sqlite native), embeddingService (native llama.rn), documentService (RNFS). Deleting the rollback/throw code makes deleteDocument not fire → the tests fail. Not false-green.
  • Fails-before / passes-after: on main, embedding failure resolved with a docId and never called deleteDocument; the 3 new cases (load-fail, embedBatch-fail/OOM, count-mismatch) fail there and pass here.
  • Known limit → why Provit: jest mocks the sqlite/native boundary, so this proves the orchestration, not a real DB round-trip. The true "doc is genuinely searchable / a bad doc surfaces an error" proof is on-device → Provit.

Provit (on-device E2E)

  • In progress. Journey being built: (1) create project → add a real doc → confirm it's actually retrievable in a project chat; (2) feed a doc that fails to embed → confirm the KB shows the error, not a silent dead entry. Will update this comment with the run result (pass/fail + device) before merge. Per CLAUDE.md, this PR is not merge-ready until that lands.

Platform parity

  • iOS + Android identical: RagService is platform-agnostic (op-sqlite + llama.rn on both), no platform branch. One test guards both; the Provit journey will run on both.

Standards

  • No UI/copy change — service-layer only; the error surfaces through the existing KnowledgeBaseScreen alert. N/A.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix RAG indexing: rollback and throw on embedding failures (no dead KB entries)

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Fail indexing when embeddings cannot be generated, instead of leaving unsearchable KB entries.
• Roll back the just-inserted document/chunks on any embedding error or count mismatch.
• Add Jest coverage for load failure, embedBatch failure, and embedding/chunk count mismatch.
Diagram

graph TD
  UI(["KB add flow"]) --> RS["RagService.indexDocument"] --> INS["Insert doc + chunks"] --> GEN["Generate embeddings"] --> SAVE["Save embeddings"] --> OK(["Doc searchable"])
  GEN --> FAIL{{"Embedding failure"}} --> RB["Rollback: deleteDocument"] --> ERR(["Throw error"])
  subgraph Legend
    direction LR
    _term(["Terminal/UI"]) ~~~ _proc["Process"] ~~~ _dec{{"Decision"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Database transaction for doc+chunks+embeddings
  • ➕ True atomicity even if deleteDocument implementation changes
  • ➕ Prevents partial inserts if process crashes mid-way
  • ➖ Requires transaction support/plumbing in ragDatabase layer
  • ➖ May be more invasive than the current targeted fix
2. Two-phase indexing (insert doc only after embeddings succeed)
  • ➕ Avoids needing rollback deletes on failures
  • ➕ Keeps DB clean by construction
  • ➖ May require generating/holding chunks and embeddings before docId exists
  • ➖ Harder if schema assumes docId for chunk insertion ordering
3. Persist an explicit indexing/embedding status (e.g., PENDING/FAILED)
  • ➕ Allows UI to show actionable state and enable retries
  • ➕ Could support future auto-backfill triggers cleanly
  • ➖ Schema and UI changes; larger scope than this fix
  • ➖ Still needs cleanup policy for permanently failed entries

Recommendation: The PR’s rollback-and-throw approach is the right minimal fix to restore “KB honesty” quickly and prevent permanently unsearchable entries. If embedding failures remain common or you want stronger invariants, consider a follow-up to wrap inserts in a DB transaction (best long-term atomicity) or add an explicit status field for richer UX and retry/backfill flows.

Files changed (2) +42 / -4

Bug fix (1) +12 / -1
index.tsRollback document on embedding failure and validate embedding/chunk count +12/-1

Rollback document on embedding failure and validate embedding/chunk count

• Adds a guard that errors when embedBatch returns a different number of embeddings than inserted chunks. On any embedding failure, deletes the just-inserted document (and its chunks) and throws a user-facing error so the KB add flow surfaces the failure instead of leaving a dead, unsearchable entry.

src/services/rag/index.ts

Tests (1) +30 / -3
index.test.tsAdd rollback-and-throw tests for embedding failures +30/-3

Add rollback-and-throw tests for embedding failures

• Replaces the prior test that allowed indexing to “succeed” without embeddings. Adds three cases asserting indexDocument throws and rolls back via deleteDocument when embedding model load fails, embedBatch fails (e.g., OOM), or the returned embedding count doesn’t match chunk count.

tests/unit/services/rag/index.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/rag/index.ts`:
- Around line 83-85: The rollback in the RAG indexing failure path can mask the
original embedding error if `ragDatabase.deleteDocument(docId)` throws,
preventing `logger.error` and the wrapped `throw` in the `index` flow from
running. Update the error handling around `ragDatabase.deleteDocument` in
`src/services/rag/index.ts` so rollback failures are caught and logged
separately, while the original embedding failure is always the one rethrown from
the `index` function with its cause preserved.
🪄 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 Plus

Run ID: 1296a0b5-1d3d-44c4-9e9f-5eee5d982f02

📥 Commits

Reviewing files that changed from the base of the PR and between a0535cd and a6adb0d.

📒 Files selected for processing (2)
  • __tests__/unit/services/rag/index.test.ts
  • src/services/rag/index.ts

Comment thread src/services/rag/index.ts
Comment on lines +83 to +85
ragDatabase.deleteDocument(docId);
logger.error('[RAG] Embedding generation failed — rolled back document:', err);
throw new Error(`Could not index "${fileName}": embeddings failed to generate. ${err instanceof Error ? err.message : String(err)}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the rollback delete so it can't mask the original failure.

If ragDatabase.deleteDocument(docId) throws, the DB error propagates instead of the embedding error, and logger.error / the wrapped throw never run — leaving exactly the stranded, undiagnosable entry this change is meant to prevent. Wrap the rollback so the underlying embedding cause is always surfaced.

🛡️ Proposed fix
-      ragDatabase.deleteDocument(docId);
-      logger.error('[RAG] Embedding generation failed — rolled back document:', err);
+      try {
+        ragDatabase.deleteDocument(docId);
+        logger.error('[RAG] Embedding generation failed — rolled back document:', err);
+      } catch (rollbackErr) {
+        logger.error('[RAG] Embedding generation failed AND rollback failed:', rollbackErr, err);
+      }
       throw new Error(`Could not index "${fileName}": embeddings failed to generate. ${err instanceof Error ? err.message : String(err)}`);
📝 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
ragDatabase.deleteDocument(docId);
logger.error('[RAG] Embedding generation failed — rolled back document:', err);
throw new Error(`Could not index "${fileName}": embeddings failed to generate. ${err instanceof Error ? err.message : String(err)}`);
try {
ragDatabase.deleteDocument(docId);
logger.error('[RAG] Embedding generation failed — rolled back document:', err);
} catch (rollbackErr) {
logger.error('[RAG] Embedding generation failed AND rollback failed:', rollbackErr, err);
}
throw new Error(`Could not index "${fileName}": embeddings failed to generate. ${err instanceof Error ? err.message : String(err)}`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/rag/index.ts` around lines 83 - 85, The rollback in the RAG
indexing failure path can mask the original embedding error if
`ragDatabase.deleteDocument(docId)` throws, preventing `logger.error` and the
wrapped `throw` in the `index` flow from running. Update the error handling
around `ragDatabase.deleteDocument` in `src/services/rag/index.ts` so rollback
failures are caught and logged separately, while the original embedding failure
is always the one rethrown from the `index` function with its cause preserved.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 25 rules

Grey Divider


Remediation recommended

1. Backfill mismatch not handled 🐞 Bug ≡ Correctness
Description
indexDocument now rejects when the embedding count doesn’t match chunk count, but backfillEmbeddings
still blindly uses embeddings[i] for each chunk. If an embedding-count mismatch ever occurs (as the
new guard suggests is possible), backfill can store empty vectors and semantic retrieval can produce
NaN scores.
Code

src/services/rag/index.ts[R66-68]

+      if (embeddings.length !== rowIds.length) {
+        throw new Error(`embedding count ${embeddings.length} does not match ${rowIds.length} chunks`);
+      }
Relevance

⭐⭐ Medium

No historical suggestions found about validating embedding array length in backfill/indexing
mismatch scenarios.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The repo shows backfillEmbeddings indexing embeddings by position without validating the array
length, and the math code assumes vectors have the same dimensionality; shorter vectors yield NaN
similarities.

src/services/rag/index.ts[93-119]
src/services/rag/database.ts[114-116]
src/services/rag/vectorMath.ts[9-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`indexDocument()` now enforces `embeddings.length === rowIds.length`, but `backfillEmbeddings()` does not validate the returned embedding count before indexing `embeddings[i]`. If `embedBatch()` returns fewer embeddings than chunks, `embeddingToBlob(undefined)` will produce an empty embedding blob, and semantic scoring can become `NaN`.

### Issue Context
- `backfillEmbeddings()` builds entries with `embedding: embeddings[i]` without bounds checks.
- `embeddingToBlob()` uses `new Float32Array(embedding)`; when `embedding` is `undefined` it results in a zero-length buffer.
- `cosineSimilarity()` multiplies `a[i] * b[i]` for `i < a.length`; with `b` shorter, this yields `NaN` and unstable sorting.

### Fix Focus Areas
- src/services/rag/index.ts[93-119]
- src/services/rag/database.ts[114-116]
- src/services/rag/vectorMath.ts[9-21]

### Suggested change
Apply the same length check used in `indexDocument()` inside `backfillEmbeddings()`. On mismatch, skip inserting embeddings (and consider logging + continuing), or delete any partially inserted embeddings for that doc if you ever change insertion semantics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Error cause not preserved 🐞 Bug ◔ Observability
Description
The new catch block throws a fresh Error string, which drops the original error object/stack as a
cause. This reduces debuggability (especially differentiating load vs native embedding failures)
even though the UI surfaces indexErr.message.
Code

src/services/rag/index.ts[R84-85]

+      logger.error('[RAG] Embedding generation failed — rolled back document:', err);
+      throw new Error(`Could not index "${fileName}": embeddings failed to generate. ${err instanceof Error ? err.message : String(err)}`);
Relevance

⭐⭐ Medium

Repo often accepts better error handling/logging, but no direct precedent on preserving Error
cause/stack on rethrow.

PR-#170

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new code wraps and rethrows, while the UI layer reads only message; preserving cause would keep
full diagnostic context without changing UI behavior.

src/services/rag/index.ts[76-86]
src/screens/KnowledgeBaseScreen.tsx[94-100]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`indexDocument()` rethrows embedding failures with a new `Error(...)` message, losing the original error as a structured cause. This makes diagnosing failures harder (stack traces, error typing) even though the message is preserved.

### Issue Context
Callers (e.g. KnowledgeBaseScreen) display `indexErr.message`, but logs/telemetry benefit from retaining the original error via `cause`.

### Fix Focus Areas
- src/services/rag/index.ts[76-86]
- src/screens/KnowledgeBaseScreen.tsx[94-100]

### Suggested change
Use `throw new Error(msg, { cause: err })` (if supported by your TS/JS target) or attach `(e as any).cause = err` before throwing. Keep the user-facing message, but preserve the original error for diagnostics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. indexDocument lacks integration rollback test 📘 Rule violation ▣ Testability
Description
ragService.indexDocument() now rolls back (deletes) the inserted document and throws when
embedding generation fails, but this PR only adds unit coverage for that new behavior. There is no
corresponding integration test asserting the rollback/throw path in the RAG integration suite,
risking regressions in the end-to-end indexing pipeline.
Code

src/services/rag/index.ts[R66-71]

+      if (embeddings.length !== rowIds.length) {
+        throw new Error(`embedding count ${embeddings.length} does not match ${rowIds.length} chunks`);
+      }
      const entries = rowIds.map((rowId, i) => ({
        chunkRowid: rowId,
        docId,
Relevance

⭐ Low

Similar “increase test coverage/add more tests” feedback was rejected (Codecov) in PR #233.

PR-#233

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The production code now throws and calls ragDatabase.deleteDocument(docId) when embedding
generation fails, which is a significant behavioral change. The PR adds new unit tests for
rollback-on-failure, but the RAG integration tests shown only cover successful embedding insertion
during indexing and do not exercise the new rollback failure path.

Rule 1573621: Require both unit and integration tests for new or significantly changed features
src/services/rag/index.ts[61-86]
tests/unit/services/rag/index.test.ts[116-147]
tests/integration/rag/ragFlow.test.ts[68-118]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ragService.indexDocument()` behavior changed to roll back the inserted document and throw on any embedding failure. The PR adds unit tests for this behavior, but does not add an integration test that exercises the same failure modes through the integration entry point and asserts the rollback occurred.

## Issue Context
The compliance requirement is to have both unit and integration tests for significantly changed feature behavior. Existing integration coverage for RAG indexing validates the happy-path embedding pipeline, but does not validate the new rollback-on-embedding-failure semantics.

## Fix Focus Areas
- src/services/rag/index.ts[66-85]
- __tests__/integration/rag/ragFlow.test.ts[68-130]
- __tests__/integration/rag/embeddingFlow.test.ts[72-107]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Non-atomic rollback deletes 🐞 Bug ☼ Reliability
Description
indexDocument now calls ragDatabase.deleteDocument(docId) on embedding failure, but deleteDocument
runs multiple DELETE statements without a transaction, so a mid-way failure can leave orphaned rows
/ partial cleanup. This can corrupt KB state on the rollback path and make later search/indexing
behavior inconsistent.
Code

src/services/rag/index.ts[R83-85]

+      ragDatabase.deleteDocument(docId);
+      logger.error('[RAG] Embedding generation failed — rolled back document:', err);
+      throw new Error(`Could not index "${fileName}": embeddings failed to generate. ${err instanceof Error ? err.message : String(err)}`);
Relevance

⭐ Low

Team previously rejected adding SQLite transactions for RAG DB operations (PR #113), suggesting low
appetite for atomic rollback changes.

PR-#113

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new rollback path calls deleteDocument during indexing failures, but the underlying delete
implementation is not atomic, unlike other DB batch operations in the same module that use explicit
transactions.

src/services/rag/index.ts[61-86]
src/services/rag/database.ts[176-181]
src/services/rag/database.ts[124-139]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`indexDocument()` now relies on `ragDatabase.deleteDocument(docId)` as a rollback mechanism when embedding generation fails. However, `deleteDocument()` performs multiple `DELETE` statements without wrapping them in a `BEGIN/COMMIT` transaction, so a failure (locked DB, disk full, etc.) can leave partial deletes and an inconsistent KB.

### Issue Context
- `indexDocument()` invokes rollback on embedding failures.
- `deleteDocument()` currently executes 3 separate deletes (embeddings → chunks → document) without atomicity.

### Fix Focus Areas
- src/services/rag/database.ts[176-181]
- src/services/rag/index.ts[76-86]

### Suggested change
Wrap `deleteDocument()` in a `BEGIN`/`COMMIT` with `ROLLBACK` on error (similar to `insertChunks()` / `insertEmbeddingsBatch()`). Optionally, enable/ensure foreign key enforcement and cascade rules, but still keep the transaction for atomic rollback behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/services/rag/index.ts
Comment on lines +66 to +68
if (embeddings.length !== rowIds.length) {
throw new Error(`embedding count ${embeddings.length} does not match ${rowIds.length} chunks`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Backfill mismatch not handled 🐞 Bug ≡ Correctness

indexDocument now rejects when the embedding count doesn’t match chunk count, but backfillEmbeddings
still blindly uses embeddings[i] for each chunk. If an embedding-count mismatch ever occurs (as the
new guard suggests is possible), backfill can store empty vectors and semantic retrieval can produce
NaN scores.
Agent Prompt
### Issue description
`indexDocument()` now enforces `embeddings.length === rowIds.length`, but `backfillEmbeddings()` does not validate the returned embedding count before indexing `embeddings[i]`. If `embedBatch()` returns fewer embeddings than chunks, `embeddingToBlob(undefined)` will produce an empty embedding blob, and semantic scoring can become `NaN`.

### Issue Context
- `backfillEmbeddings()` builds entries with `embedding: embeddings[i]` without bounds checks.
- `embeddingToBlob()` uses `new Float32Array(embedding)`; when `embedding` is `undefined` it results in a zero-length buffer.
- `cosineSimilarity()` multiplies `a[i] * b[i]` for `i < a.length`; with `b` shorter, this yields `NaN` and unstable sorting.

### Fix Focus Areas
- src/services/rag/index.ts[93-119]
- src/services/rag/database.ts[114-116]
- src/services/rag/vectorMath.ts[9-21]

### Suggested change
Apply the same length check used in `indexDocument()` inside `backfillEmbeddings()`. On mismatch, skip inserting embeddings (and consider logging + continuing), or delete any partially inserted embeddings for that doc if you ever change insertion semantics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/services/rag/index.ts
Comment on lines +84 to +85
logger.error('[RAG] Embedding generation failed — rolled back document:', err);
throw new Error(`Could not index "${fileName}": embeddings failed to generate. ${err instanceof Error ? err.message : String(err)}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

4. Error cause not preserved 🐞 Bug ◔ Observability

The new catch block throws a fresh Error string, which drops the original error object/stack as a
cause. This reduces debuggability (especially differentiating load vs native embedding failures)
even though the UI surfaces indexErr.message.
Agent Prompt
### Issue description
`indexDocument()` rethrows embedding failures with a new `Error(...)` message, losing the original error as a structured cause. This makes diagnosing failures harder (stack traces, error typing) even though the message is preserved.

### Issue Context
Callers (e.g. KnowledgeBaseScreen) display `indexErr.message`, but logs/telemetry benefit from retaining the original error via `cause`.

### Fix Focus Areas
- src/services/rag/index.ts[76-86]
- src/screens/KnowledgeBaseScreen.tsx[94-100]

### Suggested change
Use `throw new Error(msg, { cause: err })` (if supported by your TS/JS target) or attach `(e as any).cause = err` before throwing. Keep the user-facing message, but preserve the original error for diagnostics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@alichherawalla

Copy link
Copy Markdown
Collaborator Author

Closing — bundling the fix into #510 (refactor/parse-once-boundary). A falsifying red-flow test for this bug is committed on that branch (integration-level, mocks only the native boundary; verified red on HEAD, flips green when the fix lands).

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.

1 participant