fix(knowledge): a resume repaid the most expensive step it had already finished - #239
Merged
Conversation
…y finished
`_run_steps` reads the completed-step set once (`pipeline_runner.py:168`) and gates
four steps on it — `detect_changes`, `cleanup_deleted`, `project_profile`,
`cross_file_analysis`. `code_symbol_embed` wrote `complete_step` and nothing read
it, so every resume ran it again.
Measured in production 2026-08-27, tracker timings from one full rebuild:
11:37:23 code_symbol_embed: started
12:15:43 code_symbol_embed: completed 2300 s (38.3 min)
That is the most expensive step in the pipeline and expensive on purpose:
EMBEDDING_UPSERT_BATCH_SIZE is 8 to hold the worker inside its memory quota,
trading ~17% wall clock for ~552 MiB.
The consequence is worse than the waste. A full rebuild of that repository needs
about two hours; the job's ceiling cut it off inside `generate_docs`. Resuming
re-entered `code_symbol_embed` and spent the 38 minutes over, so attempt N+1
reached no further than attempt N and no number of attempts could finish. Raising
the ceiling alone does not fix that — it only moves where the loop stalls.
The pattern copied is already one step later in the same file: `generate_docs`
resumes at document granularity via `processed_doc_paths` (:970). This is the
coarse version. `ast_parse` and `graph_build` stay ungated deliberately — the first
rebuilds in-memory state nothing else supplies, the second merges into the stored
graph — and a test now fails if either joins the gated set.
Board, in the same change and both against my own earlier claims:
- CB-OPS1 RE-OPENED. It was struck on "a full re-index logged zero R14/R15", but
every run behind that claim died in or before `code_symbol_embed` — nothing had
reached the late steps where memory peaks. The first run that did: 12:32-12:34
inside `generate_docs`, `mem=1135M(100.1%)`, 4 x R14 on Standard-2X. What the
resize really bought is now stated precisely: before it, 170 x R14 AND 2 x R15
with a 1143 MiB peak against a 512 MiB quota — fatal. Now: over quota, no R15,
the process survives. Marginal, not clear.
- CB-OPS3 added. A worker restart during a repo index loses the run and nothing
retries it (measured on v279's own deploy).
Counts and the severity table are re-derived from the rows by script.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The defect
_run_stepsreads the completed-step set once (pipeline_runner.py:168) and gates four steps on it —detect_changes(:265),cleanup_deleted(:481),project_profile(:510),cross_file_analysis(:648).code_symbol_embedwritescomplete_stepand nothing reads it, so every resume runs it again.Measured twice in production on 2026-08-27, from the tracker:
It is the most expensive step in the pipeline, and expensive on purpose:
EMBEDDING_UPSERT_BATCH_SIZEis 8 to hold the worker inside its memory quota, trading ~17 % wall clock for ~552 MiB.Why this matters more than the waste
A full rebuild of that repository needs about 3.4 hours —
code_symbol_embedat ~38 min plusgenerate_docsat ~4.8 documents/minute over 758 documents. The manual job's ceiling cut it off insidegenerate_docs; resuming re-enteredcode_symbol_embedand spent the 38 minutes over, so attempt N+1 reached no further than attempt N and no number of attempts could finish. Raising the ceiling alone does not fix that — it only moves where the loop stalls.The pattern copied is already one step later in the same file:
generate_docsresumes at document granularity throughprocessed_doc_paths(:970), confirmed live — the run resumed at document 80 and is now past 110. This is the coarse version of that.ast_parseandgraph_buildstay ungated deliberately — the first rebuilds in-memory state nothing else supplies, the second merges into the stored graph so unchanged files survive — and a test fails if either joins the gated set.Board, both against my own earlier claims
CB-OPS1RE-OPENED. I struck it on "a full re-index logged zeroR14/R15". Every run behind that claim died in or beforecode_symbol_embed— nothing had reached the late steps where memory actually peaks. The first run that did: 12:32–12:34 insidegenerate_docs,mem=1135M(100.1%), 4 × R14 on Standard-2X. What the resize genuinely bought is now stated precisely: before it, 170 × R14 and 2 × R15 with a 1 143 MiB peak against a 512 MiB quota — fatal. Now: over quota, noR15, the process survives. Marginal, not clear.CB-OPS3added (supersedes #238, which I am closing): a worker restart during a repo index loses the run and nothing retries it — measured onv279's own deploy.Counts and the severity table are re-derived from the rows by script, not edited by hand.
Still open, deliberately not in this PR
repo_index_job_timeout_secondsis 3600 and that is known to be too small — a run died at exactly 3600 s insidegenerate_docs. The corrected ceiling needs a completed full rebuild to size it, and that measurement is running now under a temporaryREPO_INDEX_JOB_TIMEOUT_SECONDS=10800. Encoding a number before measuring it is the mistake this branch exists to correct, so the constant follows in its own change.Verification
pytest tests/unit/knowledge/ tests/integration/— 1014 passed, 2 skipped.ruff format --check,ruff check,mypy app/— clean.pytest tests/unit/docs/— 50 passed (the ratchets over this board's own arithmetic).