CLEAN-96: retry transient LightRAG failures without a person - #89
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Jira: CLEAN-96. Seven commits, ranch first.
Problem. Bedrock answers 503 in waves of 10-15 minutes (dev, 2026-09-17 00:14-00:27: 170 in a row). LightRAG's own retry is five attempts a few seconds apart, so it gives up inside the wave and marks the document
failed; Ranch records that and nothing heals it. An Index run or a Reindex re-uploads, LightRAG refuses the duplicate of the failed copy it still holds (Original doc_id: X, Status: failed), and the row fails again. Nine good documents sat red overnight; the fix was a person with a port-forward andPOST /documents/reprocess_failed.Change.
source/domain/indexFailure.ts: a failure message is transient (the Bedrock wrapper'sRetryError, a LightRAG 5xx, a lost connection, a refusal naming a failed original) or permanent (only whitespace, no content, anything unknown, on purpose). Three automatic retries after 5, 15 and 60 minutes, then an honestfailed.Source.indexAttempts,Source.indexRetryAt(one migration, existing rows untouched). Everyfailedwrite goes through onerecordFailure; success and a manual retry reset the count.IndexReconcileServicegets a third step: due rows on an idle pipeline go back in flight under the handle LightRAG really holds (a refusal's original, not the rejected copy) and the pipeline is nudged withreprocess_failed; a document LightRAG no longer holds is uploaded again, within the attempt budget. The nudge cooldown is now per knowledge and shared with the stalled-pipeline nudge, which also became per knowledge (it used to restart the default instance only).failedon a row it was confirming, it dropped the handle and wrote nothing, leaving the rowprocessingforever (the "Indexing… for four days" shape). It now records the failure and keeps the handle. LightRAG'supdated_aton the document tells a fresh failure from one the row was already re-queued over.retryingthrough filter, counts, DTO and admin (badge, filter option, "Attempt 2 of 4, next try 14:32" on the row).failedCountcounts terminal failures only.reindexSource, nothing called it). The single-row wait no longer marks a slow documentfailedon timeout; it leaves it in flight for the reconciler, like the batch path.retryingCounttravels next tofailedCountandprocessingCounton the knowledge, the overview and the admin, so a retrying row is neither "failed" nor "never sent". The list gateway subtracts it from the storedfailedstates so list and detail agree.updated_aton that verdict, stored on the row asindexRequeuedOverAt(a third column, same migration). Attempts are incremented in the database and a failure already recorded with a future slot is not charged twice, so an index run overlapping a reconcile pass cannot spend two retries on one verdict. One pass retries at most 50 rows.Out of scope, noted in the ticket: an Index run itself nudging the pipeline (it converges through the retry path at the cost of one refused upload and five minutes; a row that already has a retry slot is reported without spending it), and LightRAG concurrency (gitops).
Verified:
bun run build,generate:swaggerboots (DI),bunx jeston the reins slice after the last commit: 19 suites, 212 tests green; the whole API before the review fixes: 81 suites green, with the bridledocumentText.extractor.spec.tssuite red onmainfor the known pdf.js-in-jest reason, untouched here. Changed.vuefiles compile with the repo's@vue/compiler-sfc. New specs: classification (27 cases from real log lines), recording and retry paths in the gateway including the whole fail/retry/fail loop to the last attempt, the reconciler's retry step across two bases and the shared cooldown, mapper, failure summary, overview counts.On dev after the deploy: nothing to click. The next Bedrock wave should leave
retrying N source(s) in <base>: ...in the API log and rows goingretrying -> processing -> indexedon their own,failedCountstaying at zero.