Problem
When a tenant-repo ingest fails, assertErrorFreeIngestionSummary (TenantRepoSyncService.mjs) retains only the first bounded KB_* code from the summary:
const sourceCode = summary.errors
.map(item => item?.code)
.find(code => typeof code === 'string' && BOUNDED_KB_ERROR_CODE_PATTERN.test(code));A single ingest can fail for several independent reasons at once — an embed failure alongside a mirror/clone failure alongside per-file parse warnings. Reporting one of them makes a multi-cause failure read as single-cause: an operator fixes the code they were shown, the lane fails identically on the next sweep, and nothing in the log says why.
The error count is also lost. lastSourceErrorCode: KB_VECTOR_EMBED_FAILED is indistinguishable between "one file failed to embed" and "every file failed to embed" — a partial ingest and a total one look the same.
Observed live: the lane sat at consecutiveFailures = 3 with a single reported lastSourceErrorCode, and the log line carried no way to tell whether that was the whole story.
What must NOT change
The suppression of messages and details is deliberate and correct, documented at TenantRepoSyncService.mjs:229 and :708 — the latter explicitly "preserved as lastSourceErrorCode without copying raw stderr, URLs, or…". An ingestion error message can carry a clone URL with an embedded token, so copying messages would breach the credential contract (#11787's constraint: no secrets in logs, manifests, or graph-visible config).
This ticket does not touch that. Bounded codes are safe by construction — BOUNDED_KB_ERROR_CODE_PATTERN is /^KB_[A-Z0-9_]{1,120}$/, which cannot express a URL, a token, or stderr. That is precisely why the codes can widen while the messages stay uncopied.
Proposal
- Retain every distinct bounded code (
sourceErrorCodes), collapsing duplicates.
- Retain the total error count (
sourceErrorCount), including entries whose code was unbounded or absent.
- Surface both in the existing per-repo ERROR log line, so nothing added is dead:
source=<first> also=<rest> errors=<n>.
sourceErrorCode keeps its exact prior meaning (the first bounded code), so getSourceErrorCode and lastSourceErrorCode are untouched and no consumer changes.
Acceptance criteria
Out of scope
- Copying messages or details — deliberately forbidden, see above.
- Surfacing the new fields in the deployment-state snapshot per-repo entries. Worth doing, but that assembly path is distinct from the
repoStates.push site and needs tracing first; the log line is the cheap surface that makes the retained data readable today.
- The embed failure itself — #16566.
Related
- #16566 — the multi-cause failure this exists to make legible.
- #16551 — tenant sync never backs off; the failing path freezes its own counters. Adjacent reporting defect on the same lane.
Authored by @neo-opus-vega (Claude Opus 5).
Problem
When a tenant-repo ingest fails,
assertErrorFreeIngestionSummary(TenantRepoSyncService.mjs) retains only the first boundedKB_*code from the summary:const sourceCode = summary.errors .map(item => item?.code) .find(code => typeof code === 'string' && BOUNDED_KB_ERROR_CODE_PATTERN.test(code));A single ingest can fail for several independent reasons at once — an embed failure alongside a mirror/clone failure alongside per-file parse warnings. Reporting one of them makes a multi-cause failure read as single-cause: an operator fixes the code they were shown, the lane fails identically on the next sweep, and nothing in the log says why.
The error count is also lost.
lastSourceErrorCode: KB_VECTOR_EMBED_FAILEDis indistinguishable between "one file failed to embed" and "every file failed to embed" — a partial ingest and a total one look the same.Observed live: the lane sat at
consecutiveFailures = 3with a single reportedlastSourceErrorCode, and the log line carried no way to tell whether that was the whole story.What must NOT change
The suppression of messages and details is deliberate and correct, documented at
TenantRepoSyncService.mjs:229and:708— the latter explicitly "preserved aslastSourceErrorCodewithout copying raw stderr, URLs, or…". An ingestion error message can carry a clone URL with an embedded token, so copying messages would breach the credential contract (#11787's constraint: no secrets in logs, manifests, or graph-visible config).This ticket does not touch that. Bounded codes are safe by construction —
BOUNDED_KB_ERROR_CODE_PATTERNis/^KB_[A-Z0-9_]{1,120}$/, which cannot express a URL, a token, or stderr. That is precisely why the codes can widen while the messages stay uncopied.Proposal
sourceErrorCodes), collapsing duplicates.sourceErrorCount), including entries whose code was unbounded or absent.source=<first> also=<rest> errors=<n>.sourceErrorCodekeeps its exact prior meaning (the first bounded code), sogetSourceErrorCodeandlastSourceErrorCodeare untouched and no consumer changes.Acceptance criteria
sourceErrorCodesemantics unchanged — existing consumers unaffected.Out of scope
repoStates.pushsite and needs tracing first; the log line is the cheap surface that makes the retained data readable today.Related
Authored by @neo-opus-vega (Claude Opus 5).