Problem
IngestionService.ingestSourceFiles's top-level catch flattens every error that arrives without its own code:
} catch (error) {
summary.errors.push(this.createError({
code : error.code || 'KB_INGEST_FAILED',
message: error.message
}));So the most common real failure is unnameable by construction. The cause exists only in error.message, and downstream consumers deliberately refuse to copy messages (TenantRepoSyncService.mjs:229, :708) because a clone URL or a provider response can carry a credential. An operator sees KB_INGEST_FAILED and has no route to what it meant.
Observed live on the canonical plane: the tenant lane reported exactly that while the vector store was restarting underneath it — a condition with an obvious name that nothing was allowed to say.
Proposal
Classify the caught error into a bounded KB_* code inside the service, where the message is legitimately visible. Only the resulting code leaves, so diagnosis widens without touching the credential boundary.
Deliberately narrow: preserve a bounded code the error already carries, name the one case there is live evidence for (store unreachable), fall back unchanged otherwise. A taxonomy of guessed causes would trade one unnameable code for several confidently wrong ones, and a wrong code is worse than a generic one because it sends the reader somewhere specific.
Reuse note: the connection predicate already exists as a private copy inside the Knowledge Base ChromaManager. It should move to chromaClientPrimitives beside its sibling isChromaCollectionNotFoundError — the pair every caller needs to tell apart — rather than becoming a third definition.
Acceptance criteria
Out of scope
- Making any ingest succeed. This names one failure class so the next occurrence routes to a cause instead of a shrug.
- Whether the live failure is the unreachable case — that is what the next run answers once this lands.
- The materialization-stage failure — #16580 / #16577.
Related
- Parent: #16566 — tenant ingestion fails at two different stages; this is the embed/ingest stage's diagnosability leaf.
- #16575 / #16576 — multi-cause reporting; a newly-named code only reaches the operator because that merged.
- #16580 — the sibling diagnostic on the materialization stage.
Authored by @neo-opus-vega (Claude Opus 5).
Problem
IngestionService.ingestSourceFiles's top-level catch flattens every error that arrives without its owncode:} catch (error) { summary.errors.push(this.createError({ code : error.code || 'KB_INGEST_FAILED', message: error.message }));So the most common real failure is unnameable by construction. The cause exists only in
error.message, and downstream consumers deliberately refuse to copy messages (TenantRepoSyncService.mjs:229,:708) because a clone URL or a provider response can carry a credential. An operator seesKB_INGEST_FAILEDand has no route to what it meant.Observed live on the canonical plane: the tenant lane reported exactly that while the vector store was restarting underneath it — a condition with an obvious name that nothing was allowed to say.
Proposal
Classify the caught error into a bounded
KB_*code inside the service, where the message is legitimately visible. Only the resulting code leaves, so diagnosis widens without touching the credential boundary.Deliberately narrow: preserve a bounded code the error already carries, name the one case there is live evidence for (store unreachable), fall back unchanged otherwise. A taxonomy of guessed causes would trade one unnameable code for several confidently wrong ones, and a wrong code is worse than a generic one because it sends the reader somewhere specific.
Reuse note: the connection predicate already exists as a private copy inside the Knowledge Base
ChromaManager. It should move tochromaClientPrimitivesbeside its siblingisChromaCollectionNotFoundError— the pair every caller needs to tell apart — rather than becoming a third definition.Acceptance criteria
KB_*code keeps it.KB_code (ENOENTand friends) does not leak through as if it were bounded./^KB_[A-Z0-9_]{1,120}$/.Out of scope
Related
Authored by @neo-opus-vega (Claude Opus 5).