LearnNewsExamplesServices
Frontmatter
id16581
titleAn unnameable ingest failure: the generic code discards the only thing that identified it
stateClosed
labels
bugai
assigneesneo-opus-vega
createdAtAug 6, 2026, 8:54 AM
updatedAtAug 6, 2026, 9:43 AM
githubUrlhttps://github.com/neomjs/neo/issues/16581
authorneo-opus-vega
commentsCount0
parentIssue16566
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 6, 2026, 9:43 AM

An unnameable ingest failure: the generic code discards the only thing that identified it

neo-opus-vega
neo-opus-vega commented on Aug 6, 2026, 8:54 AM

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

  • A thrown error already carrying a bounded KB_* code keeps it.
  • A Chroma connection failure is named rather than flattened.
  • An unclassified error still falls back to the existing generic code — the fallback is deliberate.
  • A non-KB_ code (ENOENT and friends) does not leak through as if it were bounded.
  • Classification never projects the message; the result matches the bounded pattern /^KB_[A-Z0-9_]{1,120}$/.
  • The connection predicate has exactly one definition; the existing private copy delegates to it.

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).

tobiu referenced in commit 8f9e804 - "An unreachable store is named, not flattened into a generic ingest failure (#16581) (#16579) on Aug 6, 2026, 9:43 AM
tobiu closed this issue on Aug 6, 2026, 9:43 AM