fix(worker): the cron could rebuild a repository the button never could - #237
Merged
Conversation
`run_repo_index_task` is the repo-index pipeline, and two ARQ jobs call it with
two different ceilings — the nightly cron's `run_daily_project_knowledge_sync`
at 7200 s, and `run_repo_index`, which the "Re-index repository" button and
`POST /api/projects/{id}/index` enqueue, at 1800 s.
Both measured against the same repository, from `indexing_runs`:
08-25 22:00 completed 42.4 min nightly
08-27 09:30 TimeoutError 30.0 min manual
1800.02s ! run_repo_index failed, TimeoutError
pipeline_runner.py:1468 in _run_code_symbol_embed
2 544 s of work fits under one ceiling and can never fit under the other. So the
repository rebuilt itself unattended at 3 a.m. and failed every time a person
asked — the worse half, because the button is what an operator presses after
shipping a fix.
It had been diagnosed once already. AUD-0819-20 gave the job its own knob on
2026-08-19 for exactly this failure, then left the default at 1800 — the value
just measured as too small — noting that a repository needing longer could say
so without a code edit. Nothing in production said so. A knob defaulting to the
known-bad value moves a defect rather than closing it.
The same day's memory fix had made it worse and the link was never drawn:
`EMBEDDING_UPSERT_BATCH_SIZE` went 200 -> 8, buying ~552 MiB with ~17% more wall
clock, spent inside the step the ceiling was cutting off. That fix's caution —
raising the ceiling helps only a worker that is not swapping — was right, and
its precondition is now met and measured: Standard-2X, a full rebuild with zero
R14 and zero R15 (CB-OPS1, closed on that evidence).
`repo_index_job_timeout_seconds` now defaults to 3600, and the invariants are
tests rather than comments: it must clear the measured rebuild with 25% headroom,
it must stay below the daily sync's ceiling (which contains it plus a DB index
plus a code<->DB sync), and no long job may be registered without its own knob
again — the shape that produced AUD-0819-20.
Checked rather than assumed: the killed run's checkpoint recorded all five
completed steps in `indexing_checkpoint_step`, so a resume skips them. The empty
`completed_steps` column on the row is legacy and no longer written
(`checkpoint_service.py:10-15`).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…wiring
`test_repo_index_registered_with_its_own_configurable_timeout` asserted
`{"timeout": 1800}` as a literal. That is how a default measured as too small
came to look deliberate: AUD-0819-20 added the knob, left the value, and a green
test asserted the bad number — which then failed in production again eight days
later.
It now compares the registration against `settings.repo_index_job_timeout_seconds`,
which is the wiring the test is named for. The sibling
`test_repo_index_timeout_reads_the_setting` already proves the value is read from
config, and the value itself is argued where its measurement lives, in
`tests/unit/services/test_repo_index_ceiling.py`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 27, 2026
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.
What was measured
run_repo_index_taskis the repo-index pipeline. Two ARQ jobs call it, each carrying its own ceiling:run_daily_project_knowledge_syncPOST /api/projects/{id}/indexrun_repo_indexBoth numbers from
indexing_runs, same repository (9 981 files):2 544 s of work fits under one ceiling and can never fit under the other. The repository rebuilt itself unattended at 3 a.m. and failed every time a person asked — the worse half, because the button is what an operator presses after shipping a fix.
Why it survived a previous diagnosis
AUD-0819-20 gave the job its own knob on 2026-08-19 for this exact failure, then left the default at 1800 — the value just measured as too small — with a note that a repository needing longer could say so without a code edit. Nothing in production said so; neither
REPO_INDEX_JOB_TIMEOUT_SECONDSnor its sibling is set. A knob defaulting to the known-bad value moves a defect rather than closing it, and a test pinning{"timeout": 1800}as a literal made that default look deliberate.The same day's memory fix had made it worse and the link was never drawn:
EMBEDDING_UPSERT_BATCH_SIZEwent 200 → 8, buying ~552 MiB with ~17 % more wall clock — spent inside the step the ceiling was cutting off. That fix's caution (raising the ceiling helps only a worker that is not swapping) was right, and its precondition is now met and measured: Standard-2X, a full rebuild with zeroR14and zeroR15(CB-OPS1, closed on that evidence).The change
repo_index_job_timeout_secondsdefaults to 3600. The invariants are tests, not comments:daily_knowledge_sync_job_timeout_seconds, which contains it plus a DB index plus a code↔DB sync — asserted together with the fact that the containment still exists, so the ordering cannot quietly become arbitrary;The existing registration test now compares against the setting rather than a literal.
Checked rather than assumed
The killed run's checkpoint recorded all five completed steps in
indexing_checkpoint_step, so a resume skips them — the emptycompleted_stepscolumn on the row is legacy and no longer written (checkpoint_service.py:10-15). No second defect there.Verification
pytest tests/— full backend suite green (6 952 passed / 4 skipped / 1 xfailed locally).ruff format --check,ruff check,mypy app/— clean.CB-OPS2: force one manual full re-index and require it to reachpipeline_end. The row is struck on that evidence, not on this merge.