Skip to content

Conversation

@lukemarsden
Copy link

Tasks that fail during processing now use exponential backoff before retrying. This prevents failed tasks (e.g., cloning a non-existent repo) from immediately retrying in a tight loop. The backoff starts at 5 seconds and doubles with each retry, capping at 5 minutes.

Description

Adds exponential backoff retry logic for failed tasks in the indexing worker. When a task fails (e.g., cloning a repo that doesn't exist), it's now scheduled for retry with increasing delays: 5s → 10s → 20s → 40s → 80s → 160s → 300s (cap).

Changes:

  • Added retry_count and next_retry_at fields to the Task entity (persisted to DB)
  • Task repository next() now skips tasks waiting for retry
  • Worker service marks failed tasks for retry instead of immediately re-processing
  • Database migration adds the new columns to the tasks table

Related Issue

N/A

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Other (please describe)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Additional Notes

  • Added 17 new tests covering backoff calculation, retry behavior, and persistence
  • Retry state is persisted to survive restarts (tasks maintain their backoff schedule across deploys)
  • Migration is backwards-compatible (new columns have defaults)
  • Documentation updated in docs/reference/sync/index.md

Tasks that fail during processing now use exponential backoff before
retrying. This prevents failed tasks (e.g., cloning a non-existent repo)
from immediately retrying in a tight loop. The backoff starts at 5
seconds and doubles with each retry, capping at 5 minutes.
@github-actions
Copy link

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/kodit
   _version.py18475%15–19
   app.py78780%3–176
   cli.py511767%57–84, 99–101, 107
   config.py1112870%24, 179, 182–185, 198, 207–209, 213–215, 222–225, 245–249, 255–263
   database.py422635%25–62, 71, 76–84, 88–89, 93
   log.py1252476%60, 74, 142–146, 156–>160, 205–>198, 207–208, 213–223, 242, 244–253, 263–266, 276
   mcp.py15911422%52–56, 73–77, 177–226, 231, 244–284, 317–341, 374–394, 427–451, 484–506, 539–559, 591
   middleware.py35350%3–75
src/kodit/application/factories
   reporting_factory.py211148%20–24, 31–36
   server_factory.py24811643%199–201, 205–209, 213–215, 219–223, 229–235, 239–241, 245–249, 253–257, 261–263, 267–276, 280–282, 286–290, 294–298, 302–309, 313–524, 528–538, 542, 557–559, 565–567, 571–575, 579–583, 587–589, 593–598, 602–606, 610–612, 616–625, 629–633, 637–641, 645–649, 653–659, 663–669, 673–679
src/kodit/application/handlers
   base.py4175%11
   registry.py11546%12, 16, 20–22
src/kodit/application/handlers/commit
   api_docs.py513920%19–22, 38–43, 47–100
   architecture_discovery.py433026%27, 46–52, 56–117
   commit_description.py533334%23, 59–65, 69–132
   cookbook.py796219%30–33, 51–59, 63–164, 173–175
   create_bm25_index.py191143%15, 30–32, 36–53
   create_code_embeddings.py362430%20, 36–39, 43–75, 81–93
   create_example_code_embeddings.py362430%20, 36–39, 43–75, 83–93
   create_example_summary.py432928%22, 48–52, 56–118
   create_example_summary_embeddings.py503526%30, 48–53, 57–119, 127–137
   create_summary_embeddings.py503526%30, 48–53, 57–124, 130–142
   create_summary_enrichment.py432928%22, 44–48, 52–113
   database_schema.py483327%25, 106–112, 116–178
   extract_examples.py851283%25, 71, 103–>97, 107–>97, 144, 149–151, 155, 165–166, 171–173
   extract_snippets.py69592%25, 74, 110–112
   scan_commit.py35193%60, 69–>71
src/kodit/application/handlers/repository
   clone_repository.py311937%20, 38–44, 48–79
   delete_repository.py634919%24, 47–57, 61–95, 102–135, 141–161
   sync_repository.py37484%21, 60, 84–87
src/kodit/application/services
   code_search_application_service.py1282575%24, 40, 54, 156–171, 178–189, 195–202, 260–282
   commit_indexing_application_service.py402436%33–37, 45–74, 78–85, 89–90
   enrichment_query_service.py1294066%63, 77–82, 97–>99, 99–>101, 130, 144–156, 162, 170, 203–204, 208–209, 213–214, 220, 228–229, 235, 243–244, 248, 256–257, 279, 287–288, 304, 314–337, 356, 373–>371
   indexing_worker_service.py67670%3–131
   queue_service.py35485%54–63
   reporting.py40583%13, 72, 84, 88, 102
   repository_query_service.py977220%34–61, 68–80, 92–102, 114–138, 152–186, 190–199, 208, 213, 216, 222–237
   repository_sync_service.py801183%36, 38, 49, 69–70, 85–>87, 88, 105, 126–130, 139
   sync_scheduler.py402728%26–30, 34–36, 40–46, 50–65, 69–79
src/kodit/domain
   errors.py110%4
   protocols.py107992%37, 41, 45, 51, 55, 59, 63, 67, 82
   value_objects.py2821196%65, 92–115, 120, 125–128, 338, 645, 649
src/kodit/domain/enrichments
   enricher.py7186%17
   enrichment.py35197%41
src/kodit/domain/enrichments/architecture
   architecture.py9189%20
src/kodit/domain/enrichments/architecture/database_schema
   database_schema.py8188%17
src/kodit/domain/enrichments/architecture/physical
   formatter.py4175%11
   physical.py8188%17
src/kodit/domain/enrichments/history
   history.py9189%18
src/kodit/domain/enrichments/history/commit_description
   commit_description.py8188%17
src/kodit/domain/enrichments/usage
   cookbook.py8188%17
src/kodit/domain/entities
   __init__.py1661788%23, 40, 81, 94, 104–108, 256–258, 262–263, 285–286, 348–349
   git.py1211587%47, 53, 72, 87, 92–96, 141, 145–148, 166, 171, 188
src/kodit/domain/factories
   git_repo_factory.py17468%39–53, 79
src/kodit/domain/services
   cookbook_context_service.py1484565%105–>109, 111–>115, 116–>120, 121–>124, 153–>159, 159–>184, 163–>167, 170–>161, 174–>161, 178–>181, 184–>201, 190–>201, 195–>198, 202–208, 229–>242, 231–>229, 237, 239–240, 251–258, 262–266, 270–>280, 276–277, 282–286, 291–295, 313–337
   embedding_service.py46195%92, 125–>124
   git_repository_service.py852766%70, 91, 112–114, 156–174, 193–194, 198–207, 212, 214–215
   physical_architecture_service.py58586%132, 134, 136, 138, 140
   task_status_query_service.py11191%24
src/kodit/domain/tracking
   resolution_service.py33581%41–44, 56–57
src/kodit/infrastructure/api/client
   __init__.py330%3–6
   base.py39390%3–100
   exceptions.py440%4–19
   generated_endpoints.py20200%9–67
src/kodit/infrastructure/api/middleware
   auth.py13826%16–19, 27–31
src/kodit/infrastructure/api/v1
   dependencies.py651868%40–43, 51–54, 76, 112, 124, 136, 146, 158, 170, 182, 194, 204
src/kodit/infrastructure/api/v1/routers
   commits.py1171170%3–482
   enrichments.py54540%3–188
   queue.py16160%3–64
   repositories.py1117823%62, 71, 83–97, 112–177, 203–229, 243–>246, 244, 247, 263–270, 298–308, 351–393, 407–416
   search.py16160%3–77
src/kodit/infrastructure/api/v1/schemas
   commit.py45450%3–96
   queue.py16160%3–35
   search.py1061060%3–263
   snippet.py27270%3–58
src/kodit/infrastructure/bm25
   local_bm25_repository.py762465%22–23, 46–57, 76–77, 80–81, 88–89, 102–103, 107, 112, 137–>135, 150
   vectorchord_bm25_repository.py895532%118–120, 124–131, 135–139, 143–148, 152–154, 157–161, 165–202, 206–227, 234–237
src/kodit/infrastructure/cloning/git
   git_python_adaptor.py35213858%19–37, 42–59, 116–118, 145–147, 156–164, 194–196, 226–228, 265, 311–363, 372–401, 440–449, 461–>457, 464–468, 499, 518–522, 569, 583–588, 614–618, 628, 633–634, 649–654, 664–>exit, 682–684, 691–725
   working_copy.py53392%48–55
src/kodit/infrastructure/database_schema
   database_schema_detector.py1291119%62–80, 84–89, 93–102, 106–116, 120–129, 133–163, 167–268
src/kodit/infrastructure/embedding
   embedding_factory.py39776%64–65, 74–77, 85–86
   local_vector_search_repository.py352032%50–70, 75–88, 97
   vectorchord_vector_search_repository.py997221%100–105, 109–115, 119–120, 124–161, 167–202, 206–240, 249–260, 263–272
src/kodit/infrastructure/embedding/embedding_providers
   litellm_embedding_provider.py40390%70–78, 103–>107
   local_embedding_provider.py53292%16–17, 50–>60, 64–>78
src/kodit/infrastructure/enricher
   enricher_factory.py191135%21, 41–53
   litellm_enricher.py301347%47–79
   local_enricher.py453028%35–40, 55–121
   utils.py7522%20–30
src/kodit/infrastructure/example_extraction
   parser.py813946%39–>50, 64–70, 78–123, 135–137
src/kodit/infrastructure/git
   git_utils.py12120%3–32
src/kodit/infrastructure/ignore
   ignore_pattern_provider.py30300%3–69
src/kodit/infrastructure/mappers
   task_mapper.py12186%23
src/kodit/infrastructure/physical_architecture/detectors
   docker_compose_detector.py1473171%52, 62–63, 123–>126, 127–>131, 135–>137, 149–162, 166, 195–198, 211–221, 246–>244, 255–>exit, 262–>255, 278, 296–299, 304–305, 321, 334–336
src/kodit/infrastructure/physical_architecture/formatters
   narrative_formatter.py86593%112–113, 115–116, 145
src/kodit/infrastructure/providers
   async_batch_processor.py20286%28, 48
   litellm_provider.py761678%40–>44, 55–87
src/kodit/infrastructure/reporting
   db_progress.py11464%17–19, 23
   log_progress.py18945%18–20, 24–40
   telemetry_progress.py9278%15, 19
src/kodit/infrastructure/slicing
   api_doc_extractor.py793253%44, 48–49, 57–59, 71, 110, 126, 145–146, 155–187
   ast_analyzer.py100792%38–39, 54–56, 138, 171
   language_analyzer.py52615965%56, 78, 106, 117–>124, 121–>124, 125, 135–>144, 138–>135, 145–146, 165–168, 179, 186, 191–193, 198, 209–>214, 215, 219, 256, 276–>281, 282, 292–>297, 324, 363–365, 398, 412, 415–>420, 428, 432–443, 447–456, 460–475, 490–>485, 504, 522–527, 530, 534, 538, 562, 565–>570, 571, 578, 591–>588, 600–602, 630, 642–650, 677, 684, 697–702, 706–721, 770, 782–787, 791–806, 850–855, 859–874, 882, 914, 926–931, 935–950
   slicer.py3014978%23, 68, 75–77, 86, 90, 97, 121–>114, 133–134, 140–142, 200, 214–217, 229, 236, 252–253, 271–>286, 281–>277, 322–>332, 324–>332, 336–>335, 342–343, 370–>357, 397–418, 445–>454, 468–470, 474, 489, 515–>514, 528–>526, 534, 548, 567, 585–>581, 588
src/kodit/infrastructure/slicing/formatters
   template_formatter.py44488%22, 61–63
src/kodit/infrastructure/sqlalchemy
   embedding_repository.py84295%179, 203, 211–>219
   enrichment_association_repository.py31194%69
   enrichment_v2_repository.py651175%153–204
   entities.py205896%37, 420–426
   git_branch_repository.py36290%33, 70
   git_file_repository.py26196%68
   git_tag_repository.py35773%29, 52, 61–69
   query.py1541984%60, 62, 64, 66, 68, 71–74, 102–>104, 191–196, 235, 245, 265, 284, 340–345, 349–354
   repository.py1701191%50, 70–>78, 95, 99, 136, 187–190, 297, 328, 333
   task_repository.py71197%46, 110–>exit
   unit_of_work.py30960%29–>exit, 41–43, 47–49, 53–55
src/kodit/migrations
   env.py30300%3–85
src/kodit/migrations/versions
   4b1a3b2c8fa5_refactor_git_tracking.py51510%10–185
   04b80f802e0c_foreign_key_review.py23230%10–98
   7c3bbc2ab32b_add_embeddings_table.py15150%10–54
   7f15f878c3a1_add_new_git_entities.py1401400%10–689
   9cf0e87de578_add_queue.py15150%10–46
   9d493502b3ac_add_task_retry_fields.py15150%10–34
   9e53ea8bb3b0_add_authors.py33330%10–102
   19f8c7faf8b9_add_generic_enrichment_type.py52520%10–259
   4073b33f9436_add_file_processing_flag.py11110%10–35
   4552eb3f23ce_add_summary.py11110%10–33
   85155663351e_initial.py23230%10–97
   af4c96f50d5a_remove_branch_tag_fk_constraint.py35350%10–93
   b9cd1c3fd762_add_task_status.py21210%10–76
   c3f5137d30f5_index_all_the_things.py21210%10–49
   f9e5ef5e688f_add_git_commits_number.py15150%10–43
src/kodit/utils
   dump_config.py1821820%3–361
   dump_openapi.py25250%3–39
   generate_api_paths.py40400%4–137
   path_utils.py382917%28–57, 63–65, 70–83
TOTAL9164368056% 

Tests Skipped Failures Errors Time
371 8 💤 0 ❌ 0 🔥 53.764s ⏱️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants