Errors
All errors are exported from the core package (and re-exported by React and Expo), so you can
use instanceof:
import { WikiBusyError } from '@equationalapplications/core-llm-wiki';
try { await wiki.runPrune(entityId);} catch (e) { if (e instanceof WikiBusyError) return; // another job is running; retry later throw e;}| Error | Thrown when | Useful fields | What to do |
|---|---|---|---|
WikiBusyError |
A conflicting job (librarian, heal, ingest, prune, reembed, import, forget, or ontology backfill) is already running for the entity | operation, entityId |
Retry later. It’s usually safe to swallow. |
WikiTransactionError |
A driver error escaped a transaction | cause, sqliteErrorCode |
Alert, grouping by sqliteErrorCode |
PrunePartialFailureError |
A deletion hook left some removals unconfirmed during runPrune. Prune keeps those rows, finishes its other work, then throws. |
deleted, failedAt, remaining, deletedTasks, deletedEvents, cause |
Fix the hook, then run prune again |
WikiExternalDeletionError |
forget() ran, but your vector index didn’t confirm every removal. The local delete stays committed. |
code ('WIKI_EXTERNAL_DELETION'), entityId, unconfirmedIds (at most 100), unconfirmedCount, timedOut, cause (sanitized; absent on a timeout or when no cause is known) |
Fix the hook. The ids stay owed; retry, or call runExternalDeletions() |
WikiIngestEmptyError |
Every chunk of an ingest failed | parseFailures, sourceRef, chunks |
Check the provider’s output format |
WikiDuplicateHashError |
The same content is held under another live sourceRef |
canonical, sourceHash, entityId |
Skip, or use onDuplicateHash: 'skip' |
WikiSourceRefHashCollision |
upsertGraph hash collision |
existingSourceRef, sourceHash |
Use the existing ref, or change the content |
WikiGraphNodeOwnershipConflict |
A node ID belongs to another entity | code (no identifiers, by design) |
Use entity-qualified IDs |
WikiStrictOntologyViolation |
A strict-mode graph write used a type outside the manifest | kind, type, entityId |
Fix the data or the manifest |
WikiInvalidReadOptions |
read() options can’t be satisfied (for example, floors that sum to more than maxResults) |
field, reason |
Fix the options |
WikiDraftNotFound |
promoteDraft found no live draft with that ID |
code |
Refresh the draft list |
WikiSupersedeError |
supersede() was refused |
reason |
See Temporal → Rules |
WikiMigrationRequiredError |
setup({ migrations: 'refuse' }) found pending work |
pendingWork |
Run setup() outside the lease |
WikiSearchUnavailableError |
The keyword index failed to initialize | cause |
Writes still work. Call syncSearchIndex() to retry. |
WikiParseError |
The model’s output couldn’t be parsed | tier, position |
Usually surfaces as parseFailures rather than a throw |
Errors you throw from your own callbacks pass through with their type unchanged.
Migration v9 fails on existing duplicates
Section titled “Migration v9 fails on existing duplicates”Schema migrations run in setup(). They’re additive and safe to re-run. Migration v9
(add_source_ref_index) adds a source_ref_index table with a unique (entity_id, source_hash)
constraint on live rows, so each content hash belongs to at most one sourceRef per entity. It
closes a race where two concurrent ingests under different sourceRefs could both store the same
content. (One document’s facts all share a hash, which is fine. The rule is about sourceRefs.)
A database created before that fix may already have a hash held under two or more live
sourceRefs. Then setup() throws:
Migration v9 (add_source_ref_index) failed: existing live rows have multiple sourceRefs sharing a hash. Found N duplicate (entity_id, source_hash) groups. …The message counts the groups and shows up to five. Nothing is deleted, the backfill doesn’t
run, and the schema version doesn’t advance, so it’s safe to retry. (Under the default
migrations: 'allow' policy, setup() has already created an empty source_ref_index.)
The message names hashes, not sourceRefs. List each group’s sourceRefs with this read-only
query:
SELECT entity_id, source_hash, GROUP_CONCAT(DISTINCT source_ref) AS source_refsFROM llm_wiki_entriesWHERE deleted_at IS NULL AND source_hash IS NOT NULLGROUP BY entity_id, source_hashHAVING COUNT(DISTINCT source_ref) > 1;Pick the sourceRef to keep in each group, forget every other one within that group, then run
setup() again:
// once per losing sourceRef in each group; sourceHash scopes the delete to that groupawait wiki.forget(entityId, { sourceRef: loser, sourceHash: group.source_hash });await wiki.setup();forget() soft-deletes the rows (and records outbox DELETE events when enableOutbox is on),
like any other forget. Three cautions:
- Always pass the group’s
sourceHash. Without it,forget()removes every live row of thatsourceRefin the entity, across all hashes, so a ref that loses one group but is the keeper of another would lose its kept rows too. The error message shows only a 12-character hash prefix; take the fullsource_hashfrom the listing query. forget()normalizes thesourceRefyou pass (to letters, digits,.,_,-and space). Legacy rows on a pre-v9 store may not be normalized yet, because the normalize pass runs only after migrations succeed. If a listedsourceRefcontains any other character (a/, say), it may match nothing (deleted.entriesis0) or match a different ref. Use the SQL fallback below for those rows.GROUP_CONCATjoins refs with commas, so a legacy ref containing a comma is ambiguous in the listing. Check such groups withSELECT DISTINCT source_ref …instead. Before the release that fixed #316,forget()worked here only after somesetup()under'allow'had opened the database; it now works on any open, including one refused under'refuse'or'auto'.
If you don’t have a WikiMemory instance, delete each losing sourceRef’s live rows directly
instead, once per losing (entityId, sourceRef, sourceHash) in each group, then run setup():
BEGIN;DELETE FROM llm_wiki_entriesWHERE entity_id = :entityId AND source_ref = :sourceRef AND source_hash = :sourceHash AND deleted_at IS NULL;COMMIT;This hard-deletes the rows and emits no outbox events. The prefix in your database may differ
from llm_wiki_, so match the table names in your schema. Either way, the kept sourceRef is
untouched and v9’s duplicate check passes on the next setup().