Skip to content

Claim exclusive ownership for concurrent rebase continue/abort - #2004

Open
timsehn wants to merge 6 commits into
masterfrom
fix/rebase-concurrent-continue-abort
Open

Claim exclusive ownership for concurrent rebase continue/abort#2004
timsehn wants to merge 6 commits into
masterfrom
fix/rebase-concurrent-continue-abort

Conversation

@timsehn

@timsehn timsehn commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes concurrent dolt_rebase('--continue') vs dolt_rebase('--abort') both failing.

Before: both connections saw session isRebasing, both ran multi-step cleanup, both could report rebase failed / rebase recovery failed.

After: one side claims the end of the rebase under the graph lock (reload durable working-set state → clear isRebasing + persist → drop dolt_rebase). The loser gets no rebase in progress. Cleanup after claim is idempotent if the temp working branch is already gone.

Matches Dolt’s abortRebase / validateActiveRebase spirit: once working-set rebase state is cleared, the other session should see no active rebase.

Test plan

  • multi_process_merge_rebase_test — 44/44 (includes new Test 5: 12 concurrent continue/abort trials)
  • test/doltlite_rebase.sh — 24/24
  • pthread sequential abort→continue / continue→abort: loser always no rebase in progress

Tim added 2 commits August 5, 2026 19:20
When --continue and --abort race, both could pass the in-session
isRebasing check and then both fail mid-cleanup ("rebase failed" /
"rebase recovery failed"), leaving recovery unpredictable.

Add rebaseClaimActiveEnd: under the graph lock, reload durable working
set state; if isRebasing is already clear return SQLITE_DONE ("no rebase
in progress"); otherwise clear the flag, persist it, and drop
dolt_rebase so only one side proceeds. Abort and continue both claim
before cleanup/replay. Make post-claim cleanup idempotent when the
temporary working branch is already gone, so a lost race reports
"no rebase in progress" instead of a stuck recovery failure.

Absent plan table also reports "no rebase in progress" for consistency.

Test: multi_process_merge_rebase_test races continue vs abort across
forked connections for 12 trials.
Checked builds use -Werror=unused-result; ignore-free write() on the
child result pipe failed CI on Ubuntu.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

DoltLite source coverage

Metric Covered Total Coverage
Lines 43129 51175 84.28%
Branches 18929 28338 66.80%
Functions 2103 2303 91.32%

Merged 165 pooled raw profiles from the distributed Linux correctness jobs.

Per-file coverage (91 files)
File Lines Branches Functions
src/btree_orig_api.c 85.87% 76.19% 87.18%
src/chunk_file.c 100.00% 100.00% 100.00%
src/chunk_index.c 88.76% 76.47% 100.00%
src/chunk_refs.c 99.35% 85.48% 100.00%
src/chunk_staging.c 87.50% 77.05% 94.12%
src/chunk_store.c 91.01% 75.00% 100.00%
src/chunk_store_commit.c 86.32% 66.96% 100.00%
src/chunk_store_lock.c 94.25% 77.47% 100.00%
src/chunk_store_refs_api.c 91.52% 77.40% 100.00%
src/chunk_wal.c 94.24% 75.00% 92.86%
src/doltlite.c 100.00% 100.00% 100.00%
src/doltlite_add.c 85.19% 67.71% 100.00%
src/doltlite_ancestor.c 88.68% 66.11% 100.00%
src/doltlite_at.c 77.98% 59.93% 94.12%
src/doltlite_blame.c 80.70% 58.94% 96.43%
src/doltlite_branch.c 86.78% 80.20% 100.00%
src/doltlite_branches.c 93.51% 77.27% 92.86%
src/doltlite_checkout.c 76.38% 59.81% 100.00%
src/doltlite_cherry_pick.c 70.53% 55.08% 71.43%
src/doltlite_chunk_walk.c 88.60% 64.19% 100.00%
src/doltlite_cmd.c 69.23% 60.83% 93.33%
src/doltlite_commit.c 93.22% 73.08% 100.00%
src/doltlite_commit_ancestors.c 90.65% 68.89% 92.31%
src/doltlite_commit_cmd.c 75.72% 69.42% 100.00%
src/doltlite_config.c 77.54% 71.43% 100.00%
src/doltlite_conflicts.c 81.83% 58.92% 93.44%
src/doltlite_constraint_violations.c 81.56% 55.91% 92.86%
src/doltlite_core.c 91.69% 72.22% 100.00%
src/doltlite_creds.c 84.01% 54.75% 89.74%
src/doltlite_dbpage.c 92.68% 73.21% 91.67%
src/doltlite_diff.c 81.99% 63.68% 92.86%
src/doltlite_diff_stat.c 94.15% 75.38% 95.56%
src/doltlite_diff_table.c 93.78% 68.45% 97.14%
src/doltlite_gc.c 77.11% 56.01% 96.30%
src/doltlite_hashof.c 75.64% 62.86% 94.44%
src/doltlite_history.c 85.61% 70.55% 100.00%
src/doltlite_http_remote.c 82.22% 57.17% 92.31%
src/doltlite_ignore.c 90.06% 66.67% 100.00%
src/doltlite_log.c 94.38% 70.16% 92.31%
src/doltlite_merge.c 96.94% 74.49% 100.00%
src/doltlite_merge_cmd.c 88.89% 73.50% 100.00%
src/doltlite_merge_constraints.c 88.25% 67.65% 100.00%
src/doltlite_merge_constraints_check.c 92.68% 65.76% 100.00%
src/doltlite_merge_constraints_fk.c 77.09% 55.86% 85.71%
src/doltlite_merge_constraints_unique.c 86.50% 64.37% 100.00%
src/doltlite_merge_pass1.c 91.02% 74.87% 100.00%
src/doltlite_merge_pass2.c 62.90% 57.69% 100.00%
src/doltlite_merge_rows.c 88.25% 69.37% 100.00%
src/doltlite_merge_schema.c 84.75% 63.03% 96.55%
src/doltlite_merge_status.c 91.98% 73.53% 92.31%
src/doltlite_patch.c 94.92% 74.25% 97.83%
src/doltlite_rebase.c 84.14% 57.44% 100.00%
src/doltlite_record.c 75.90% 57.54% 92.86%
src/doltlite_ref.c 96.97% 77.27% 100.00%
src/doltlite_remote.c 81.98% 63.65% 95.92%
src/doltlite_remote_sql.c 63.89% 55.98% 92.86%
src/doltlite_remotesrv.c 75.94% 64.12% 90.62%
src/doltlite_reset.c 86.10% 71.53% 100.00%
src/doltlite_revert.c 79.29% 68.18% 100.00%
src/doltlite_schema_diff.c 92.38% 69.46% 96.88%
src/doltlite_schemas.c 68.97% 44.87% 90.91%
src/doltlite_status.c 92.46% 71.84% 97.22%
src/doltlite_tag.c 80.07% 56.62% 93.33%
src/doltlite_tls.c 85.04% 62.73% 92.31%
src/doltlite_verify_constraints.c 74.11% 58.46% 100.00%
src/doltlite_workspace.c 89.46% 65.50% 100.00%
src/pager_shim.c 58.00% 64.73% 32.37%
src/prolly_btree.c 81.21% 63.06% 86.40%
src/prolly_btree_catalog.c 80.81% 66.17% 96.67%
src/prolly_btree_cursor.c 86.32% 62.69% 97.92%
src/prolly_btree_cursor_count.c 78.78% 55.70% 100.00%
src/prolly_btree_cursor_payload.c 78.81% 60.38% 94.12%
src/prolly_btree_cursor_seek.c 73.88% 61.69% 82.35%
src/prolly_btree_mutation.c 85.68% 65.15% 97.92%
src/prolly_btree_orig.c 83.44% 35.71% 87.67%
src/prolly_btree_state.c 93.89% 67.48% 100.00%
src/prolly_btree_txn.c 82.27% 69.21% 98.04%
src/prolly_cache.c 93.24% 69.70% 100.00%
src/prolly_check.c 60.36% 62.96% 100.00%
src/prolly_chunker.c 94.04% 78.38% 100.00%
src/prolly_cursor.c 89.62% 80.95% 100.00%
src/prolly_diff.c 52.48% 38.62% 60.87%
src/prolly_hash.c 93.65% 80.00% 100.00%
src/prolly_hashset.c 90.48% 80.56% 100.00%
src/prolly_mutate.c 87.07% 78.08% 100.00%
src/prolly_mutmap.c 92.95% 80.22% 100.00%
src/prolly_node.c 89.25% 73.64% 100.00%
src/prolly_three_way_diff.c 95.58% 85.90% 100.00%
src/prolly_three_way_merge.c 80.78% 66.67% 91.67%
src/prolly_xxhash.c 100.00% 100.00% 100.00%
src/sortkey.c 93.06% 80.14% 100.00%

Download HTML and LCOV artifacts from this workflow run.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

DoltLite performance vs PR base

  • Baseline: b92cd62873d350b7da2fb28e06a31ecea744618c
  • Candidate: 6b78048801d4b7e2a921c80018d1ee0d1f0b8f85
  • Overall ratio: 0.987x
  • Gate result: PASS
  • Gates: individual > 1.25x with more than 5.00ms regression; section, suite, or overall > 1.15x with the same minimum delta
  • vc individual gate: > 1.50x with more than 25.00ms regression
  • Confirmed failed gates: none
  • Automatic retries: none
Suite Workloads Baseline total Candidate total Ratio Result
blobpk 69 11.95s 11.74s 0.982x PASS
compositepk 69 12.14s 11.93s 0.982x PASS
int 69 11.03s 11.01s 0.998x PASS
textpk 69 11.80s 11.60s 0.983x PASS
vc 13 915.37ms 914.33ms 0.999x PASS
blobpk details
Section Test Baseline Candidate Delta Ratio Result
mem_reads oltp_point_select 37.24ms 37.27ms +21us 1.001x PASS
mem_reads oltp_range_select 14.10ms 14.10ms -5us 1.000x PASS
mem_reads oltp_sum_range 13.86ms 14.12ms +253us 1.018x PASS
mem_reads oltp_order_range 3.13ms 3.18ms +53us 1.017x PASS
mem_reads oltp_distinct_range 4.23ms 4.21ms -19us 0.996x PASS
mem_reads oltp_index_scan 6.29ms 6.27ms -23us 0.996x PASS
mem_reads select_random_points 21.49ms 20.73ms -769us 0.964x PASS
mem_reads select_random_ranges 5.18ms 5.24ms +52us 1.010x PASS
mem_reads covering_index_scan 4.59ms 4.61ms +20us 1.004x PASS
mem_reads groupby_scan 33.95ms 33.97ms +16us 1.000x PASS
mem_reads index_join 9.87ms 9.58ms -294us 0.970x PASS
mem_reads index_join_scan 5.44ms 5.37ms -72us 0.987x PASS
mem_reads types_table_scan 1.25s 1.20s -46.38ms 0.963x PASS
mem_reads table_scan 1.39s 1.36s -33.70ms 0.976x PASS
mem_reads oltp_read_only 135.92ms 135.42ms -499us 0.996x PASS
mem_writes oltp_bulk_insert 353.15ms 358.65ms +5.50ms 1.016x PASS
mem_writes oltp_insert 39.40ms 39.52ms +121us 1.003x PASS
mem_writes oltp_update_index 128.81ms 128.02ms -789us 0.994x PASS
mem_writes oltp_update_non_index 84.76ms 84.75ms -9us 1.000x PASS
mem_writes oltp_delete_insert 102.12ms 103.00ms +877us 1.009x PASS
mem_writes oltp_write_only 61.13ms 61.09ms -47us 0.999x PASS
mem_writes types_delete_insert 54.27ms 53.94ms -326us 0.994x PASS
mem_writes oltp_read_write 140.60ms 140.76ms +158us 1.001x PASS
file_reads oltp_point_select 62.12ms 61.68ms -437us 0.993x PASS
file_reads oltp_range_select 16.87ms 16.78ms -86us 0.995x PASS
file_reads oltp_sum_range 16.65ms 16.74ms +85us 1.005x PASS
file_reads oltp_order_range 3.54ms 3.58ms +36us 1.010x PASS
file_reads oltp_distinct_range 4.57ms 4.58ms +7us 1.002x PASS
file_reads oltp_index_scan 9.07ms 8.99ms -81us 0.991x PASS
file_reads select_random_points 24.54ms 24.63ms +93us 1.004x PASS
file_reads select_random_ranges 7.75ms 7.72ms -32us 0.996x PASS
file_reads covering_index_scan 7.22ms 7.25ms +30us 1.004x PASS
file_reads groupby_scan 34.39ms 34.43ms +39us 1.001x PASS
file_reads index_join 11.24ms 11.10ms -135us 0.988x PASS
file_reads index_join_scan 5.78ms 5.74ms -35us 0.994x PASS
file_reads types_table_scan 1.25s 1.22s -28.63ms 0.977x PASS
file_reads table_scan 1.43s 1.37s -51.16ms 0.964x PASS
file_reads oltp_read_only 173.79ms 172.64ms -1.15ms 0.993x PASS
file_writes oltp_bulk_insert 377.76ms 383.36ms +5.60ms 1.015x PASS
file_writes oltp_insert 51.82ms 51.90ms +77us 1.001x PASS
file_writes oltp_update_index 167.40ms 166.50ms -900us 0.995x PASS
file_writes oltp_update_non_index 109.06ms 110.80ms +1.74ms 1.016x PASS
file_writes oltp_delete_insert 131.18ms 131.22ms +46us 1.000x PASS
file_writes oltp_write_only 82.20ms 83.81ms +1.60ms 1.019x PASS
file_writes types_delete_insert 71.84ms 71.57ms -267us 0.996x PASS
file_writes oltp_read_write 165.92ms 161.77ms -4.15ms 0.975x PASS
ac_reads oltp_point_select 61.66ms 61.44ms -226us 0.996x PASS
ac_reads oltp_range_select 16.87ms 16.79ms -76us 0.995x PASS
ac_reads oltp_sum_range 16.66ms 16.52ms -146us 0.991x PASS
ac_reads oltp_order_range 3.48ms 3.50ms +19us 1.005x PASS
ac_reads oltp_distinct_range 4.58ms 4.57ms -11us 0.998x PASS
ac_reads oltp_index_scan 9.06ms 9.03ms -33us 0.996x PASS
ac_reads select_random_points 24.79ms 23.74ms -1.05ms 0.958x PASS
ac_reads select_random_ranges 7.70ms 7.77ms +76us 1.010x PASS
ac_reads covering_index_scan 7.23ms 7.21ms -11us 0.998x PASS
ac_reads groupby_scan 34.23ms 34.56ms +322us 1.009x PASS
ac_reads index_join 11.25ms 11.20ms -57us 0.995x PASS
ac_reads index_join_scan 5.81ms 5.70ms -113us 0.981x PASS
ac_reads types_table_scan 1.27s 1.22s -43.49ms 0.966x PASS
ac_reads table_scan 1.40s 1.38s -20.11ms 0.986x PASS
ac_reads oltp_read_only 175.37ms 171.23ms -4.14ms 0.976x PASS
ac_writes oltp_bulk_insert_ac 80.38ms 80.73ms +356us 1.004x PASS
ac_writes oltp_insert_ac 100.41ms 101.31ms +905us 1.009x PASS
ac_writes oltp_update_index_ac 112.49ms 117.83ms +5.34ms 1.047x PASS
ac_writes oltp_update_non_index_ac 89.19ms 89.26ms +72us 1.001x PASS
ac_writes oltp_delete_insert_ac 100.37ms 108.71ms +8.34ms 1.083x PASS
ac_writes oltp_write_only_ac 104.23ms 103.69ms -540us 0.995x PASS
ac_writes types_delete_insert_ac 93.40ms 92.39ms -1.01ms 0.989x PASS
ac_writes oltp_read_write_ac 112.83ms 109.61ms -3.22ms 0.971x PASS
compositepk details
Section Test Baseline Candidate Delta Ratio Result
mem_reads oltp_point_select 41.18ms 40.45ms -732us 0.982x PASS
mem_reads oltp_range_select 20.99ms 20.93ms -59us 0.997x PASS
mem_reads oltp_sum_range 20.65ms 20.89ms +240us 1.012x PASS
mem_reads oltp_order_range 3.90ms 3.88ms -26us 0.993x PASS
mem_reads oltp_distinct_range 4.94ms 4.86ms -78us 0.984x PASS
mem_reads oltp_index_scan 6.20ms 6.32ms +123us 1.020x PASS
mem_reads select_random_points 31.93ms 32.16ms +234us 1.007x PASS
mem_reads select_random_ranges 8.89ms 8.93ms +36us 1.004x PASS
mem_reads covering_index_scan 4.40ms 4.33ms -74us 0.983x PASS
mem_reads groupby_scan 39.18ms 39.11ms -70us 0.998x PASS
mem_reads index_join 10.59ms 10.46ms -126us 0.988x PASS
mem_reads index_join_scan 5.35ms 5.28ms -66us 0.988x PASS
mem_reads types_table_scan 1.25s 1.22s -33.66ms 0.973x PASS
mem_reads table_scan 1.41s 1.38s -31.47ms 0.978x PASS
mem_reads oltp_read_only 169.80ms 170.44ms +638us 1.004x PASS
mem_writes oltp_bulk_insert 358.03ms 357.17ms -858us 0.998x PASS
mem_writes oltp_insert 36.56ms 36.69ms +127us 1.003x PASS
mem_writes oltp_update_index 116.30ms 115.70ms -598us 0.995x PASS
mem_writes oltp_update_non_index 83.06ms 82.69ms -370us 0.996x PASS
mem_writes oltp_delete_insert 94.92ms 95.53ms +617us 1.007x PASS
mem_writes oltp_write_only 57.85ms 57.53ms -315us 0.995x PASS
mem_writes types_delete_insert 55.29ms 54.55ms -742us 0.987x PASS
mem_writes oltp_read_write 155.06ms 156.75ms +1.70ms 1.011x PASS
file_reads oltp_point_select 64.89ms 65.04ms +155us 1.002x PASS
file_reads oltp_range_select 23.96ms 23.81ms -156us 0.993x PASS
file_reads oltp_sum_range 23.71ms 23.64ms -72us 0.997x PASS
file_reads oltp_order_range 4.22ms 4.27ms +44us 1.010x PASS
file_reads oltp_distinct_range 5.30ms 5.29ms -11us 0.998x PASS
file_reads oltp_index_scan 8.92ms 9.01ms +88us 1.010x PASS
file_reads select_random_points 35.59ms 35.29ms -298us 0.992x PASS
file_reads select_random_ranges 11.81ms 11.79ms -20us 0.998x PASS
file_reads covering_index_scan 6.97ms 7.01ms +41us 1.006x PASS
file_reads groupby_scan 39.44ms 39.60ms +165us 1.004x PASS
file_reads index_join 12.80ms 12.48ms -319us 0.975x PASS
file_reads index_join_scan 5.91ms 5.78ms -129us 0.978x PASS
file_reads types_table_scan 1.25s 1.21s -42.00ms 0.966x PASS
file_reads table_scan 1.40s 1.37s -30.61ms 0.978x PASS
file_reads oltp_read_only 206.56ms 207.25ms +696us 1.003x PASS
file_writes oltp_bulk_insert 378.67ms 378.30ms -372us 0.999x PASS
file_writes oltp_insert 46.46ms 45.55ms -911us 0.980x PASS
file_writes oltp_update_index 141.49ms 140.70ms -795us 0.994x PASS
file_writes oltp_update_non_index 103.86ms 104.12ms +267us 1.003x PASS
file_writes oltp_delete_insert 119.21ms 118.52ms -695us 0.994x PASS
file_writes oltp_write_only 77.47ms 77.03ms -437us 0.994x PASS
file_writes types_delete_insert 68.34ms 68.16ms -184us 0.997x PASS
file_writes oltp_read_write 174.57ms 174.81ms +242us 1.001x PASS
ac_reads oltp_point_select 65.18ms 65.42ms +234us 1.004x PASS
ac_reads oltp_range_select 24.07ms 23.87ms -200us 0.992x PASS
ac_reads oltp_sum_range 23.76ms 23.55ms -210us 0.991x PASS
ac_reads oltp_order_range 4.23ms 4.23ms -3us 0.999x PASS
ac_reads oltp_distinct_range 5.34ms 5.33ms -6us 0.999x PASS
ac_reads oltp_index_scan 9.01ms 8.86ms -156us 0.983x PASS
ac_reads select_random_points 35.45ms 35.47ms +14us 1.000x PASS
ac_reads select_random_ranges 11.80ms 11.78ms -23us 0.998x PASS
ac_reads covering_index_scan 6.96ms 7.05ms +91us 1.013x PASS
ac_reads groupby_scan 39.55ms 39.45ms -98us 0.998x PASS
ac_reads index_join 12.44ms 12.43ms -4us 1.000x PASS
ac_reads index_join_scan 5.95ms 5.76ms -184us 0.969x PASS
ac_reads types_table_scan 1.25s 1.22s -25.39ms 0.980x PASS
ac_reads table_scan 1.42s 1.38s -38.76ms 0.973x PASS
ac_reads oltp_read_only 211.09ms 208.96ms -2.13ms 0.990x PASS
ac_writes oltp_bulk_insert_ac 86.92ms 85.44ms -1.48ms 0.983x PASS
ac_writes oltp_insert_ac 106.78ms 104.42ms -2.36ms 0.978x PASS
ac_writes oltp_update_index_ac 114.63ms 118.70ms +4.07ms 1.036x PASS
ac_writes oltp_update_non_index_ac 96.75ms 88.40ms -8.35ms 0.914x PASS
ac_writes oltp_delete_insert_ac 104.09ms 108.82ms +4.73ms 1.045x PASS
ac_writes oltp_write_only_ac 105.61ms 101.68ms -3.93ms 0.963x PASS
ac_writes types_delete_insert_ac 93.25ms 95.13ms +1.88ms 1.020x PASS
ac_writes oltp_read_write_ac 115.38ms 115.48ms +101us 1.001x PASS
int details
Section Test Baseline Candidate Delta Ratio Result
mem_reads oltp_point_select 27.60ms 27.57ms -29us 0.999x PASS
mem_reads oltp_range_select 11.73ms 11.74ms +8us 1.001x PASS
mem_reads oltp_sum_range 11.23ms 11.31ms +73us 1.006x PASS
mem_reads oltp_order_range 2.88ms 2.83ms -54us 0.981x PASS
mem_reads oltp_distinct_range 3.88ms 3.86ms -24us 0.994x PASS
mem_reads oltp_index_scan 4.75ms 4.78ms +26us 1.005x PASS
mem_reads select_random_points 10.88ms 10.87ms -8us 0.999x PASS
mem_reads select_random_ranges 3.94ms 3.96ms +15us 1.004x PASS
mem_reads covering_index_scan 3.97ms 4.08ms +112us 1.028x PASS
mem_reads groupby_scan 34.13ms 34.29ms +162us 1.005x PASS
mem_reads index_join 7.72ms 7.61ms -113us 0.985x PASS
mem_reads index_join_scan 4.71ms 4.67ms -32us 0.993x PASS
mem_reads types_table_scan 1.26s 1.26s +5.19ms 1.004x PASS
mem_reads table_scan 1.37s 1.37s +3.36ms 1.002x PASS
mem_reads oltp_read_only 115.07ms 114.19ms -885us 0.992x PASS
mem_writes oltp_bulk_insert 242.75ms 239.74ms -3.01ms 0.988x PASS
mem_writes oltp_insert 28.05ms 28.05ms +0us 1.000x PASS
mem_writes oltp_update_index 105.16ms 104.38ms -778us 0.993x PASS
mem_writes oltp_update_non_index 58.16ms 58.11ms -44us 0.999x PASS
mem_writes oltp_delete_insert 78.44ms 77.90ms -540us 0.993x PASS
mem_writes oltp_write_only 44.84ms 44.51ms -334us 0.993x PASS
mem_writes types_delete_insert 39.54ms 39.69ms +150us 1.004x PASS
mem_writes oltp_read_write 104.44ms 103.74ms -702us 0.993x PASS
file_reads oltp_point_select 54.65ms 55.38ms +724us 1.013x PASS
file_reads oltp_range_select 14.83ms 14.71ms -122us 0.992x PASS
file_reads oltp_sum_range 14.24ms 14.39ms +147us 1.010x PASS
file_reads oltp_order_range 3.24ms 3.17ms -70us 0.978x PASS
file_reads oltp_distinct_range 4.21ms 4.14ms -69us 0.984x PASS
file_reads oltp_index_scan 7.99ms 7.99ms +1us 1.000x PASS
file_reads select_random_points 14.19ms 14.21ms +19us 1.001x PASS
file_reads select_random_ranges 6.79ms 6.86ms +72us 1.011x PASS
file_reads covering_index_scan 7.04ms 7.12ms +88us 1.013x PASS
file_reads groupby_scan 34.58ms 34.66ms +83us 1.002x PASS
file_reads index_join 9.54ms 9.44ms -106us 0.989x PASS
file_reads index_join_scan 5.10ms 5.02ms -81us 0.984x PASS
file_reads types_table_scan 1.26s 1.26s -2.31ms 0.998x PASS
file_reads table_scan 1.37s 1.37s +3.59ms 1.003x PASS
file_reads oltp_read_only 155.40ms 154.85ms -549us 0.996x PASS
file_writes oltp_bulk_insert 262.75ms 258.74ms -4.01ms 0.985x PASS
file_writes oltp_insert 35.41ms 35.10ms -303us 0.991x PASS
file_writes oltp_update_index 126.36ms 126.68ms +316us 1.003x PASS
file_writes oltp_update_non_index 80.14ms 80.18ms +49us 1.001x PASS
file_writes oltp_delete_insert 96.58ms 96.49ms -97us 0.999x PASS
file_writes oltp_write_only 65.19ms 64.28ms -917us 0.986x PASS
file_writes types_delete_insert 52.64ms 52.76ms +119us 1.002x PASS
file_writes oltp_read_write 123.94ms 123.83ms -109us 0.999x PASS
ac_reads oltp_point_select 55.64ms 56.31ms +675us 1.012x PASS
ac_reads oltp_range_select 14.75ms 14.78ms +38us 1.003x PASS
ac_reads oltp_sum_range 14.34ms 14.44ms +97us 1.007x PASS
ac_reads oltp_order_range 3.25ms 3.22ms -26us 0.992x PASS
ac_reads oltp_distinct_range 4.24ms 4.26ms +19us 1.004x PASS
ac_reads oltp_index_scan 8.24ms 8.16ms -75us 0.991x PASS
ac_reads select_random_points 14.74ms 14.29ms -454us 0.969x PASS
ac_reads select_random_ranges 6.92ms 6.97ms +51us 1.007x PASS
ac_reads covering_index_scan 7.48ms 7.42ms -63us 0.992x PASS
ac_reads groupby_scan 34.90ms 34.98ms +78us 1.002x PASS
ac_reads index_join 9.83ms 9.64ms -184us 0.981x PASS
ac_reads index_join_scan 5.19ms 5.09ms -94us 0.982x PASS
ac_reads types_table_scan 1.26s 1.25s -9.23ms 0.993x PASS
ac_reads table_scan 1.39s 1.38s -9.64ms 0.993x PASS
ac_reads oltp_read_only 157.77ms 156.85ms -918us 0.994x PASS
ac_writes oltp_bulk_insert_ac 64.91ms 65.36ms +453us 1.007x PASS
ac_writes oltp_insert_ac 85.38ms 81.63ms -3.75ms 0.956x PASS
ac_writes oltp_update_index_ac 95.83ms 98.19ms +2.36ms 1.025x PASS
ac_writes oltp_update_non_index_ac 75.20ms 77.54ms +2.34ms 1.031x PASS
ac_writes oltp_delete_insert_ac 88.94ms 85.60ms -3.34ms 0.962x PASS
ac_writes oltp_write_only_ac 84.76ms 85.27ms +511us 1.006x PASS
ac_writes types_delete_insert_ac 73.15ms 78.06ms +4.91ms 1.067x PASS
ac_writes oltp_read_write_ac 89.91ms 89.57ms -336us 0.996x PASS
textpk details
Section Test Baseline Candidate Delta Ratio Result
mem_reads oltp_point_select 37.81ms 37.60ms -212us 0.994x PASS
mem_reads oltp_range_select 14.08ms 14.13ms +48us 1.003x PASS
mem_reads oltp_sum_range 13.76ms 13.92ms +166us 1.012x PASS
mem_reads oltp_order_range 3.13ms 3.11ms -17us 0.995x PASS
mem_reads oltp_distinct_range 4.18ms 4.19ms +8us 1.002x PASS
mem_reads oltp_index_scan 6.06ms 6.10ms +34us 1.006x PASS
mem_reads select_random_points 20.60ms 20.14ms -459us 0.978x PASS
mem_reads select_random_ranges 5.10ms 5.13ms +24us 1.005x PASS
mem_reads covering_index_scan 4.40ms 4.41ms +15us 1.003x PASS
mem_reads groupby_scan 33.75ms 33.82ms +66us 1.002x PASS
mem_reads index_join 8.81ms 8.79ms -25us 0.997x PASS
mem_reads index_join_scan 5.51ms 5.21ms -298us 0.946x PASS
mem_reads types_table_scan 1.25s 1.21s -42.18ms 0.966x PASS
mem_reads table_scan 1.38s 1.36s -18.26ms 0.987x PASS
mem_reads oltp_read_only 136.96ms 134.96ms -2.00ms 0.985x PASS
mem_writes oltp_bulk_insert 354.43ms 355.90ms +1.47ms 1.004x PASS
mem_writes oltp_insert 39.36ms 39.03ms -329us 0.992x PASS
mem_writes oltp_update_index 132.33ms 130.97ms -1.36ms 0.990x PASS
mem_writes oltp_update_non_index 86.14ms 85.55ms -595us 0.993x PASS
mem_writes oltp_delete_insert 101.98ms 101.50ms -482us 0.995x PASS
mem_writes oltp_write_only 60.29ms 60.58ms +291us 1.005x PASS
mem_writes types_delete_insert 54.65ms 54.42ms -228us 0.996x PASS
mem_writes oltp_read_write 139.85ms 138.66ms -1.19ms 0.991x PASS
file_reads oltp_point_select 62.81ms 62.72ms -90us 0.999x PASS
file_reads oltp_range_select 17.02ms 16.93ms -88us 0.995x PASS
file_reads oltp_sum_range 16.56ms 16.79ms +227us 1.014x PASS
file_reads oltp_order_range 3.50ms 3.47ms -23us 0.993x PASS
file_reads oltp_distinct_range 4.62ms 4.55ms -65us 0.986x PASS
file_reads oltp_index_scan 8.84ms 9.09ms +247us 1.028x PASS
file_reads select_random_points 24.12ms 24.31ms +193us 1.008x PASS
file_reads select_random_ranges 7.69ms 7.76ms +73us 1.009x PASS
file_reads covering_index_scan 7.16ms 7.21ms +50us 1.007x PASS
file_reads groupby_scan 34.63ms 34.41ms -223us 0.994x PASS
file_reads index_join 11.26ms 11.16ms -98us 0.991x PASS
file_reads index_join_scan 5.75ms 5.88ms +125us 1.022x PASS
file_reads types_table_scan 1.24s 1.21s -28.65ms 0.977x PASS
file_reads table_scan 1.38s 1.34s -46.18ms 0.967x PASS
file_reads oltp_read_only 172.83ms 171.59ms -1.24ms 0.993x PASS
file_writes oltp_bulk_insert 378.66ms 385.35ms +6.69ms 1.018x PASS
file_writes oltp_insert 51.04ms 51.99ms +947us 1.019x PASS
file_writes oltp_update_index 165.81ms 166.42ms +609us 1.004x PASS
file_writes oltp_update_non_index 110.75ms 110.80ms +55us 1.000x PASS
file_writes oltp_delete_insert 132.11ms 131.74ms -375us 0.997x PASS
file_writes oltp_write_only 83.76ms 83.84ms +81us 1.001x PASS
file_writes types_delete_insert 73.30ms 73.16ms -142us 0.998x PASS
file_writes oltp_read_write 162.31ms 161.75ms -553us 0.997x PASS
ac_reads oltp_point_select 62.63ms 62.97ms +337us 1.005x PASS
ac_reads oltp_range_select 17.04ms 16.94ms -105us 0.994x PASS
ac_reads oltp_sum_range 16.78ms 17.12ms +347us 1.021x PASS
ac_reads oltp_order_range 3.46ms 3.45ms -11us 0.997x PASS
ac_reads oltp_distinct_range 4.58ms 4.54ms -34us 0.993x PASS
ac_reads oltp_index_scan 8.99ms 8.98ms -5us 0.999x PASS
ac_reads select_random_points 24.02ms 24.60ms +586us 1.024x PASS
ac_reads select_random_ranges 7.67ms 7.79ms +126us 1.016x PASS
ac_reads covering_index_scan 7.19ms 7.22ms +39us 1.005x PASS
ac_reads groupby_scan 34.71ms 34.50ms -212us 0.994x PASS
ac_reads index_join 11.27ms 11.14ms -131us 0.988x PASS
ac_reads index_join_scan 5.75ms 5.66ms -99us 0.983x PASS
ac_reads types_table_scan 1.24s 1.21s -23.01ms 0.981x PASS
ac_reads table_scan 1.37s 1.34s -33.98ms 0.975x PASS
ac_reads oltp_read_only 172.68ms 172.39ms -293us 0.998x PASS
ac_writes oltp_bulk_insert_ac 80.67ms 79.65ms -1.02ms 0.987x PASS
ac_writes oltp_insert_ac 94.16ms 94.62ms +465us 1.005x PASS
ac_writes oltp_update_index_ac 112.28ms 109.49ms -2.79ms 0.975x PASS
ac_writes oltp_update_non_index_ac 90.38ms 88.03ms -2.35ms 0.974x PASS
ac_writes oltp_delete_insert_ac 100.31ms 99.56ms -748us 0.993x PASS
ac_writes oltp_write_only_ac 97.73ms 99.53ms +1.80ms 1.018x PASS
ac_writes types_delete_insert_ac 89.26ms 88.17ms -1.09ms 0.988x PASS
ac_writes oltp_read_write_ac 107.65ms 105.23ms -2.42ms 0.977x PASS
vc details
Section Test Baseline Candidate Delta Ratio Result
vc status_clean_many_tables 81.31ms 80.60ms -713us 0.991x PASS
vc status_dirty_many_tables 84.29ms 84.05ms -241us 0.997x PASS
vc diff_regular_working_one_table 77.05ms 76.91ms -143us 0.998x PASS
vc diff_regular_working_many_tables 89.40ms 89.13ms -269us 0.997x PASS
vc diff_stat_working_many_tables 88.80ms 89.26ms +463us 1.005x PASS
vc diff_schema_working_many_tables 89.30ms 89.46ms +158us 1.002x PASS
vc branch_list_many_branches 21.94ms 21.88ms -65us 0.997x PASS
vc branch_create_delete 24.91ms 24.85ms -60us 0.998x PASS
vc checkout_branch_clean 55.08ms 54.10ms -981us 0.982x PASS
vc merge_data_no_conflicts 28.61ms 28.87ms +262us 1.009x PASS
vc merge_schema_no_conflicts 22.22ms 22.00ms -218us 0.990x PASS
vc merge_data_conflicts 126.08ms 126.72ms +641us 1.005x PASS
vc merge_data_conflicts_with_resolve 126.38ms 126.50ms +128us 1.001x PASS

All relative performance gates passed.

@itoqa

itoqa Bot commented Aug 6, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: adbbb61: 18 test cases ran, 18 passed ✅.

Summary

Coverage focused on safe completion and cancellation of repository rebase operations, including concurrent requests, stale connections, reopening, cleanup, error recovery, malformed state, and bounded retries. Both normal workflows and adversarial timing and fault conditions remained healthy, with the repository consistently usable for later commits.

Safe to merge — the exercised rebase, cleanup, concurrency, persistence, and error-handling behaviors showed no PR-attributable regressions or failures. A small number of specialized fault scenarios were not exercised, but they are coverage caveats rather than merge blockers.

Tests run by Ito

View full run

Result Severity Type Description
Cleanup Aborting the reopened rebase completed successfully even after the temporary branch had already been removed. The plan and durable rebase state were cleared, and the repository accepted a later commit.
Cleanup The concurrent cleanup check passed all 12 trials. A peer commit stayed intact, temporary rebase state was removed, and a later commit succeeded.
Cleanup The rebase cleanup completed successfully. After reopening the database, the old temporary branch was still gone and normal repository use remained possible.
Cleanup The abort and continue checks returned a recovery error when temporary-branch deletion failed for a reason other than a missing branch. The repository did not report that cleanup had succeeded.
Contract The local continue-versus-abort race completed all 44 checks successfully. Across 12 trials, exactly one side completed each terminal operation, no recovery failure was reported, and the database accepted a later commit.
Contract Ending a rebase from competing connections completed cleanly. The local checks reported 44 passed, 0 failed, and no invalid terminal result.
Contract The rebase race completed all 44 checks successfully. Continue and abort never both won, and the database stayed usable for a later commit.
Contract Concurrent continue and abort operations finished with one valid winner in every trial, and the repository accepted a new commit after each race.
Contract Plan-read, invalid-plan, conflict recovery, and recovery-failure checks all passed. The repository kept its durable state and returned the expected error category in all 50 assertions.
Contract The rebase regression checks passed in all 12 peer-commit trials and all 44 concurrency checks. Only one terminal operation was accepted in each race, no duplicate winner was reported, and a follow-up commit still worked.
Durable The local multi-process rebase regression completed successfully with 44 checks passed and 0 failures. The rebuilt database test covered durable rebase state across concurrent terminal operations and exited successfully.
Durable Continuing with a malformed rebase plan returned the expected schema error before changing the rebase. The malformed plan, active working branch, and recovery state remained available.
Durable The available local checks completed successfully, and no user-visible rebase failure was found. The requested plan cleanup failure could not be injected through the available test runner, so the fault-specific behavior remains unexercised.
Durable The available concurrency checks passed, and no stale rebase state was shown to replay after another operation finished. The specific pause-and-remove scenario could not be run because the local test setup has no synchronization hook before the rebase claim.
Rebase Continue finished the rebase successfully, cleared the rebase state, removed the temporary branch, and allowed a later commit to succeed.
Rebase Abort and continue raced across 12 trials. One operation won each time, the other reported no active rebase, and a later commit still succeeded.
Rebase A connection left open after another connection finished the rebase could not replay or undo the result. The follow-up commit checks also passed.
Rebase The rebase regression finished in 3.317 seconds under the 10-second limit. All 44 checks passed, with no bad outcomes, and the database remained usable for later work.

Tip

Reply with @itoqa to send us feedback on this test run.

Concurrent --continue can leave abort's best-effort cleanup racy after
isRebasing is already cleared. Once the claim wins, report
"Interactive rebase aborted" regardless of cleanup noise. On claim
hard-failure, re-check durable state and prefer "no rebase in progress"
when a peer already ended the rebase.
@itoqa

itoqa Bot commented Aug 6, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Reportadbbb619c289df: 21 test cases ran, 2 regressions ❌, 1 new failure ❌, 17 passing ✅, 1 additional finding ⚠️.

Diff Summary

Coverage spans normal interactive rebase completion and abort flows, durable state across reopen, concurrent terminal operations, cleanup safety, branch preservation, conflict handling, and adversarial storage-failure cases. The normal and concurrency behavior is broadly healthy, but recovery-error reporting remains unreliable when abort cleanup or state verification fails.

Not safe to merge yet — this PR has multiple attributable regressions and new failures in abort recovery handling that can hide storage or state-refresh errors while leaving cleanup incomplete or the repository state uncertain. An unrelated pre-existing recovery-reporting issue is a flag for later, but the concentration of PR-related failures makes this a merge blocker.

Tests run by Ito

View full run

Result State Severity Type Description
🆕 Regression Medium severity Cleanup The injected cleanup error was reproduced by the native regression harness, but abort returned "Interactive rebase aborted" instead of the expected "rebase recovery failed" error. The harness recorded 11 passing checks and one failed assertion for this recovery-failure message.
🆕 Regression Medium severity Contract The abort operation hit the injected storage failure, but the result did not contain the expected recovery error. The test's recovery-failure assertion failed because the operation instead treated the cleared durable state as proof that no rebase was in progress.
❌ New Failure Medium severity Durable The recovery regression completed 11 of 12 checks, but rebase_abort_recovery_failure_is_returned failed. The abort operation did not return the required 'ERROR: rebase recovery failed' result.
Passing Claim When the database could not be opened, abort returned an error and did not claim the rebase. After the database was restored, the active plan and temporary branch were still present.
Passing Claim Concurrent rebase cleanup completed without errors, and a fresh connection could still use the database afterward.
Passing Claim Concurrent rebase commands completed successfully. The losing command did not leave the database locked, and a new connection could continue using it.
Passing Claim After repeated terminal operations compete for the graph lock, the shared database can be reopened and a new commit succeeds.
Passing Cleanup Aborting after another connection removed the temporary branch completed successfully. The repository stayed usable, and a new commit could still be made.
Passing Cleanup Aborting the rebase did not overwrite an independent change on the return branch. The local regression suite completed all 24 checks successfully.
Passing Cleanup After terminal cleanup removed the temporary branch, reopening the local database did not bring it back. Follow-up continue, abort, and commit operations remained safe, with all 44 checks passing.
Passing Cleanup Aborting an interactive rebase returned success, left the repository on the expected branch, and removed the rebase plan and temporary branch.
Passing Contract The repository handled all 12 continue-versus-abort races successfully. Every trial completed without a recovery error, hang, or lost child result, and the harness finished all 44 assertions.
Passing Contract Running abort and continue in either order completed the first operation and made the second report that no rebase was in progress. The suite passed all 24 assertions and left no stale plan or temporary branch.
Passing Contract Concurrent rebase cleanup completed successfully, and every trial allowed a new row to be inserted and committed afterward.
Passing Contract The cleanup and reopen checks passed all 47 assertions. Abort returned success after a plan-read failure, restored durable state after a conflict, and left no stale active rebase after reopening the database.
Passing Durable The concurrent continue and abort checks completed all 44 checks. Across 12 races, exactly one operation won, the loser did not corrupt the rebase state, and the final repository stayed usable.
Passing Durable A replay conflict stops the operation once, clears the old rebase state, and restores the repository. Retrying from a fresh connection does not replay the cleared work, and a later commit remains possible.
Passing Durable Reopening the database preserves the active rebase until it is safely aborted, malformed plan schemas stay blocked, and plan-read errors do not leave partial state. All 42 checks passed across the three regression tests.
Passing Rebase Abort and continue were run at the same time across 12 trials. One operation won each trial, the losing operation did not report recovery failure, and the repository accepted a new commit afterward.
Passing Rebase The local rebase regression checks passed all 24 cases, including terminal cleanup after reopening the database. No replay or branch change was observed after rebase completion, although the shell suite did not directly exercise two live stale connections.
⏸️ Skipped Contract The rebase race completed all 44 checks successfully. Continue and abort never both won, and the database stayed usable for a later commit.
⏸️ Skipped Contract The rebase regression checks passed in all 12 peer-commit trials and all 44 concurrency checks. Only one terminal operation was accepted in each race, no duplicate winner was reported, and a follow-up commit still worked.
⏸️ Skipped Durable The local multi-process rebase regression completed successfully with 44 checks passed and 0 failures. The rebuilt database test covered durable rebase state across concurrent terminal operations and exited successfully.
⏸️ Skipped Durable Continuing with a malformed rebase plan returned the expected schema error before changing the rebase. The malformed plan, active working branch, and recovery state remained available.
⏸️ Skipped Rebase Continue finished the rebase successfully, cleared the rebase state, removed the temporary branch, and allowed a later commit to succeed.
⏸️ Skipped Rebase The rebase regression finished in 3.317 seconds under the 10-second limit. All 44 checks passed, with no bad outcomes, and the database remained usable for later work.
⚠️ Additional Finding Medium severity Durable Starting an abort with a storage error shows a generic rebase failure instead of the recovery failure message.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟡 Abort hides a rebase recovery failure
  • Severity: Medium Medium severity
  • Description: Starting an abort with a storage error shows a generic rebase failure instead of the recovery failure message.
  • Impact: During an abort, a storage failure is reported as a generic rebase error instead of a recovery failure. Users may not know that recovery is incomplete and may take the wrong next steps.
  • Steps to Reproduce:
    1. Create a repository with an active interactive rebase from feat onto main.
    2. Inject fault 953 so the DROP TABLE operation used while claiming the end of the rebase returns an I/O error.
    3. Run SELECT dolt_rebase('--abort').
    4. Check the returned error text and confirm that it says rebase recovery failed rather than only rebase failed.
  • Stub / mock content: Fault 953 was intentionally injected to simulate a DROP TABLE I/O failure during the targeted recovery regression; no application mocks, route intercepts, or bypasses were used.
  • Code Analysis: rebaseClaimActiveEnd in src/doltlite_rebase.c:1070-1083 first clears and saves the session rebase state, then calls sqlite3FaultSim(953) at line 1081 and converts the simulated DROP TABLE failure into SQLITE_IOERR at line 1083. doltliteRebaseInteractiveAbort calls rebaseClaimActiveEndRetry at lines 1542-1544, but it sets bPlanDropped only after SQLITE_OK at line 1550. The injected SQLITE_IOERR therefore takes the abort_err branch with bPlanDropped still zero. Lines 1640-1647 then free the branch names and return the generic "rebase failed" message without calling rebaseResultRecoveryFailure. The regression oracle at test/doltlite_regression_test_c.c:6948-6959 requires "ERROR: rebase recovery failed" and rejects a success message, so the implementation and oracle disagree deterministically. The smallest fix is to preserve the fact that durable termination was claimed before the DROP TABLE failure, or otherwise route this post-clear failure through the recovery-failure path instead of the pre-claim generic-error branch. The PR diff only changes the later post-claim cleanup handling around src/doltlite_rebase.c:1411 and does not change this claim/error path.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread src/doltlite_rebase.c

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

🔁 Regression: previously passing at adbbb61

Medium severity Abort hides cleanup failures

What failed: The injected cleanup error was reproduced by the native regression harness, but abort returned "Interactive rebase aborted" instead of the expected "rebase recovery failed" error. The harness recorded 11 passing checks and one failed assertion for this recovery-failure message.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: When a storage error interrupts abort cleanup, users are told the rebase was aborted successfully even though cleanup may be incomplete. They may need to retry or manually recover the repository state.
  • Steps to Reproduce:
    1. Create a repository with main and feat branches, then start an interactive rebase on feat.
    2. Inject a non-SQLITE_NOTFOUND error while abort removes the temporary rebase branch.
    3. Run the interactive rebase abort operation and capture its result.
    4. Check the temporary branch and working-set state after the operation.
  • Stub / mock content: The native regression harness used SQLite fault injection to simulate a non-NOTFOUND ref deletion error. No application routes, external services, or production data were used.
  • Code Analysis: The cleanup helper rebaseCleanupAfterClaim in src/doltlite_rebase.c:1101-1127 preserves the first error from doltliteMutateRefs and doltlitePersistWorkingSet, so a ref deletion error other than SQLITE_NOTFOUND remains a failure. The only allowed idempotent exception is explicit in rebaseDeleteWorkingBranchRefs at src/doltlite_rebase.c:1021-1027, where SQLITE_NOTFOUND is converted to SQLITE_OK. In the PR-changed abort path at src/doltlite_rebase.c:1434-1442, the return value from rebaseCleanupAfterClaim is explicitly cast to void, and the return values from rebaseRestoreReturnBranchWorkingState, doltlitePersistWorkingSet, and doltliteVcSealBranchStyleTxn are also discarded before src/doltlite_rebase.c:1447 always returns "Interactive rebase aborted". The regression test in test/doltlite_regression_test_c.c:6948-6959 installs fault code 953 for the abort cleanup path and requires an ERROR: rebase recovery failed result; that assertion failed. The smallest practical fix is to retain the cleanup return code after the durable claim, return rebaseResultRecoveryFailure when a non-NOTFOUND cleanup error occurs, and continue treating only SQLITE_NOTFOUND as successful idempotent cleanup. The post-claim claim ownership should remain durable, so this fix only restores truthful error reporting rather than undoing the claim.
  • Why this is likely a bug: This is not only a runtime symptom: the source records non-NOTFOUND cleanup errors, while the abort caller deliberately throws that result away and unconditionally emits a success message. The native fault-injection test directly exercises the error path and fails exactly where the public error contract requires recovery failure. A real I/O or storage error can therefore be hidden from the caller, leaving cleanup incomplete while presenting a successful terminal result. The PR diff directly introduced the discarded cleanup-result behavior at src/doltlite_rebase.c:1434-1442, so the smallest fix is to propagate that retained error while preserving the intended success behavior for a missing temporary ref.
Relevant code

src/doltlite_rebase.c:1021-1027

rc = chunkStoreDeleteBranch(cs, zWorkingBranch);
return rc==SQLITE_NOTFOUND ? SQLITE_OK : rc;

src/doltlite_rebase.c:1104-1127

static int rebaseCleanupAfterClaim(
  sqlite3 *db,
  const char *zOrigBranch,
  const char *zWorkingBranch
){
  ...
  rebaseKeepFirstError(&rc, rc2);
  ...
  return rc;
}

src/doltlite_rebase.c:1434-1447

(void)rebaseCleanupAfterClaim(db, zOrigBranch, zWorking);
...
(void)doltlitePersistWorkingSet(db);
(void)doltliteVcSealBranchStyleTxn(db);
...
sqlite3_result_text(context, "Interactive rebase aborted", -1, SQLITE_STATIC);

test/doltlite_regression_test_c.c:6948-6959

gRegressionFaultCode = 953;
...
res = queryScalarText(db, "SELECT dolt_rebase('--abort')");
...
check("rebase_abort_recovery_failure_is_returned",
      strstr(res, "ERROR: rebase recovery failed")!=0);
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Abort hides cleanup failures**

**What failed:** The injected cleanup error was reproduced by the native regression harness, but abort returned "Interactive rebase aborted" instead of the expected "rebase recovery failed" error. The harness recorded 11 passing checks and one failed assertion for this recovery-failure message.

- **Impact:** When a storage error interrupts abort cleanup, users are told the rebase was aborted successfully even though cleanup may be incomplete. They may need to retry or manually recover the repository state.
- **Steps to reproduce:**
  1. Create a repository with main and feat branches, then start an interactive rebase on feat.
  2. Inject a non-SQLITE_NOTFOUND error while abort removes the temporary rebase branch.
  3. Run the interactive rebase abort operation and capture its result.
  4. Check the temporary branch and working-set state after the operation.
- **Stub / mock content:** The native regression harness used SQLite fault injection to simulate a non-NOTFOUND ref deletion error. No application routes, external services, or production data were used.
- **Code analysis:** The cleanup helper rebaseCleanupAfterClaim in src/doltlite_rebase.c:1101-1127 preserves the first error from doltliteMutateRefs and doltlitePersistWorkingSet, so a ref deletion error other than SQLITE_NOTFOUND remains a failure. The only allowed idempotent exception is explicit in rebaseDeleteWorkingBranchRefs at src/doltlite_rebase.c:1021-1027, where SQLITE_NOTFOUND is converted to SQLITE_OK. In the PR-changed abort path at src/doltlite_rebase.c:1434-1442, the return value from rebaseCleanupAfterClaim is explicitly cast to void, and the return values from rebaseRestoreReturnBranchWorkingState, doltlitePersistWorkingSet, and doltliteVcSealBranchStyleTxn are also discarded before src/doltlite_rebase.c:1447 always returns "Interactive rebase aborted". The regression test in test/doltlite_regression_test_c.c:6948-6959 installs fault code 953 for the abort cleanup path and requires an ERROR: rebase recovery failed result; that assertion failed. The smallest practical fix is to retain the cleanup return code after the durable claim, return rebaseResultRecoveryFailure when a non-NOTFOUND cleanup error occurs, and continue treating only SQLITE_NOTFOUND as successful idempotent cleanup. The post-claim claim ownership should remain durable, so this fix only restores truthful error reporting rather than undoing the claim.
- **Why this is likely a bug:** This is not only a runtime symptom: the source records non-NOTFOUND cleanup errors, while the abort caller deliberately throws that result away and unconditionally emits a success message. The native fault-injection test directly exercises the error path and fails exactly where the public error contract requires recovery failure. A real I/O or storage error can therefore be hidden from the caller, leaving cleanup incomplete while presenting a successful terminal result. The PR diff directly introduced the discarded cleanup-result behavior at src/doltlite_rebase.c:1434-1442, so the smallest fix is to propagate that retained error while preserving the intended success behavior for a missing temporary ref.

**Relevant code:**

`src/doltlite_rebase.c:1021-1027`

~~~c
rc = chunkStoreDeleteBranch(cs, zWorkingBranch);
return rc==SQLITE_NOTFOUND ? SQLITE_OK : rc;
~~~

`src/doltlite_rebase.c:1104-1127`

~~~c
static int rebaseCleanupAfterClaim(
  sqlite3 *db,
  const char *zOrigBranch,
  const char *zWorkingBranch
){
  ...
  rebaseKeepFirstError(&rc, rc2);
  ...
  return rc;
}
~~~

`src/doltlite_rebase.c:1434-1447`

~~~c
(void)rebaseCleanupAfterClaim(db, zOrigBranch, zWorking);
...
(void)doltlitePersistWorkingSet(db);
(void)doltliteVcSealBranchStyleTxn(db);
...
sqlite3_result_text(context, "Interactive rebase aborted", -1, SQLITE_STATIC);
~~~

`test/doltlite_regression_test_c.c:6948-6959`

~~~c
gRegressionFaultCode = 953;
...
res = queryScalarText(db, "SELECT dolt_rebase('--abort')");
...
check("rebase_abort_recovery_failure_is_returned",
      strstr(res, "ERROR: rebase recovery failed")!=0);
~~~

Comment thread src/doltlite_rebase.c
goto claim_done;
}

rc = doltliteClearSessionRebaseState(db);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

🔁 Regression: previously passing at adbbb61

Medium severity Abort hides rebase recovery errors

What failed: The abort operation hit the injected storage failure, but the result did not contain the expected recovery error. The test's recovery-failure assertion failed because the operation instead treated the cleared durable state as proof that no rebase was in progress.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: When an abort encounters a storage failure, users are told that no rebase is in progress instead of being warned that recovery failed. Rebase state can remain behind, requiring manual recovery.
  • Steps to Reproduce:
    1. Create a repository with main and feat branches, then start an interactive rebase from feat onto main.
    2. Inject storage fault 953 while the abort operation drops the rebase plan after clearing durable rebase state.
    3. Run the rebase abort operation and inspect its returned error text.
    4. Compare the result with the expected recovery-failure message and confirm it does not report successful abort completion.
  • Stub / mock content: The test used an intentional native storage-fault injection to exercise recovery handling; no application mocks, route stubs, or source bypasses were applied.
  • Code Analysis: In /tmp/output-agent-workspace/repo/src/doltlite_rebase.c, rebaseClaimActiveEnd() clears and persists the durable rebase state at lines 1070-1073, then calls sqlite3FaultSim(953) before DROP TABLE at lines 1080-1083. That makes fault 953 a claim-adjacent error: the rebase flag is already cleared, but removing main.dolt_rebase has failed. The PR diff changes doltliteRebaseInteractiveAbort() at lines 1413-1429. After any non-OK claim result, it force-refreshes and reloads the working set, then returns no rebase in progress when stillRebasing is false. Because fault 953 occurs after the durable flag was cleared, this new branch suppresses rebaseResultRecoveryFailure(context, rc), which is the required error path. The smallest fix is to distinguish a peer-lost claim (SQLITE_DONE) from a claim error after local durable termination began, or otherwise preserve the claim error for fault 953 instead of converting every refreshed clear flag into no rebase.
  • Why this is likely a bug: The targeted native test passed 11 of 12 assertions and confirmed that fault 953 was injected; only the required recovery-error category failed. The source path explains the exact mismatch without relying on the unavailable browser service: the changed abort handler uses the post-failure cleared flag as a peer-loss signal even when this same operation cleared it and then failed to drop the plan. This can hide a real storage/recovery failure and leave main.dolt_rebase behind, so it is a production error-handling defect rather than test setup noise. A targeted fix should preserve recovery failure for errors returned after local durable claim work, while retaining no rebase in progress for a genuine SQLITE_DONE peer win.
Relevant code

src/doltlite_rebase.c:1070-1083

rc = doltliteClearSessionRebaseState(db); ... rc = doltliteSaveWorkingSet(db); ... rcDrop = sqlite3FaultSim(953) ? SQLITE_IOERR : sqlite3_exec(db, "DROP TABLE IF EXISTS main.dolt_rebase", 0, 0, 0);

src/doltlite_rebase.c:1413-1429

if( rc!=SQLITE_OK ){ ... doltliteGetSessionRebaseState(db, &stillRebasing, 0, 0, 0, 0); ... if( !stillRebasing ) sqlite3_result_error(context, "no rebase in progress", -1); else rebaseResultRecoveryFailure(context, rc); }

test/doltlite_regression_test_c.c:6948-6959

gRegressionFaultCode = 953; ... res = queryScalarText(db, "SELECT dolt_rebase('--abort')"); ... check("rebase_abort_recovery_failure_is_returned", strstr(res, "ERROR: rebase recovery failed")!=0);
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Abort hides rebase recovery errors**

**What failed:** The abort operation hit the injected storage failure, but the result did not contain the expected recovery error. The test's recovery-failure assertion failed because the operation instead treated the cleared durable state as proof that no rebase was in progress.

- **Impact:** When an abort encounters a storage failure, users are told that no rebase is in progress instead of being warned that recovery failed. Rebase state can remain behind, requiring manual recovery.
- **Steps to reproduce:**
  1. Create a repository with main and feat branches, then start an interactive rebase from feat onto main.
  2. Inject storage fault 953 while the abort operation drops the rebase plan after clearing durable rebase state.
  3. Run the rebase abort operation and inspect its returned error text.
  4. Compare the result with the expected recovery-failure message and confirm it does not report successful abort completion.
- **Stub / mock content:** The test used an intentional native storage-fault injection to exercise recovery handling; no application mocks, route stubs, or source bypasses were applied.
- **Code analysis:** In /tmp/output-agent-workspace/repo/src/doltlite_rebase.c, rebaseClaimActiveEnd() clears and persists the durable rebase state at lines 1070-1073, then calls sqlite3FaultSim(953) before DROP TABLE at lines 1080-1083. That makes fault 953 a claim-adjacent error: the rebase flag is already cleared, but removing main.dolt_rebase has failed. The PR diff changes doltliteRebaseInteractiveAbort() at lines 1413-1429. After any non-OK claim result, it force-refreshes and reloads the working set, then returns no rebase in progress when stillRebasing is false. Because fault 953 occurs after the durable flag was cleared, this new branch suppresses rebaseResultRecoveryFailure(context, rc), which is the required error path. The smallest fix is to distinguish a peer-lost claim (SQLITE_DONE) from a claim error after local durable termination began, or otherwise preserve the claim error for fault 953 instead of converting every refreshed clear flag into no rebase.
- **Why this is likely a bug:** The targeted native test passed 11 of 12 assertions and confirmed that fault 953 was injected; only the required recovery-error category failed. The source path explains the exact mismatch without relying on the unavailable browser service: the changed abort handler uses the post-failure cleared flag as a peer-loss signal even when this same operation cleared it and then failed to drop the plan. This can hide a real storage/recovery failure and leave main.dolt_rebase behind, so it is a production error-handling defect rather than test setup noise. A targeted fix should preserve recovery failure for errors returned after local durable claim work, while retaining no rebase in progress for a genuine SQLITE_DONE peer win.

**Relevant code:**

`src/doltlite_rebase.c:1070-1083`

~~~c
rc = doltliteClearSessionRebaseState(db); ... rc = doltliteSaveWorkingSet(db); ... rcDrop = sqlite3FaultSim(953) ? SQLITE_IOERR : sqlite3_exec(db, "DROP TABLE IF EXISTS main.dolt_rebase", 0, 0, 0);
~~~

`src/doltlite_rebase.c:1413-1429`

~~~c
if( rc!=SQLITE_OK ){ ... doltliteGetSessionRebaseState(db, &stillRebasing, 0, 0, 0, 0); ... if( !stillRebasing ) sqlite3_result_error(context, "no rebase in progress", -1); else rebaseResultRecoveryFailure(context, rc); }
~~~

`test/doltlite_regression_test_c.c:6948-6959`

~~~c
gRegressionFaultCode = 953; ... res = queryScalarText(db, "SELECT dolt_rebase('--abort')"); ... check("rebase_abort_recovery_failure_is_returned", strstr(res, "ERROR: rebase recovery failed")!=0);
~~~

Comment thread src/doltlite_rebase.c
zBranch = doltliteGetSessionBranch(db);
if( !zBranch || !zBranch[0] ) zBranch = "main";

rc = chunkStoreLockAndRefresh(cs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

🆕 New Failure: identified in this diff run

Medium severity Abort hides refresh failures during recovery

What failed: The recovery regression completed 11 of 12 checks, but rebase_abort_recovery_failure_is_returned failed. The abort operation did not return the required 'ERROR: rebase recovery failed' result.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: When a rebase abort cannot verify durable state, users receive a generic failure instead of a recovery-failure message. They may not know that the rebase state is unverified and needs recovery guidance.
  • Steps to Reproduce:
    1. Create a repository with main and feat branches and start an interactive rebase from feat onto main.
    2. Inject a non-BUSY claim error followed by a force-refresh or durable working-set reload failure.
    3. Run dolt_rebase('--abort').
    4. Check the returned error and the persisted isRebasing state.
  • Stub / mock content: The test used controlled SQLite fault injection to model a hard claim and refresh failure. No production services, customer data, or application-route mocks were used.
  • Code Analysis: The new claim path in rebaseClaimActiveEnd() calls chunkStoreLockAndRefresh(), chunkStoreForceRefresh(), and doltliteLoadWorkingSet() at src/doltlite_rebase.c:1057-1063. Any non-OK result exits before the durable claim completes. The caller then handles the claim result at src/doltlite_rebase.c:1404-1447, where the PR added a refresh-and-reload check before deciding whether the peer won; if those checks cannot read durable state, the code must retain the original claim error and call rebaseResultRecoveryFailure(), not downgrade the result to a generic rebase failure or infer that no rebase is active. The continue path also demonstrates the unsafe split at src/doltlite_rebase.c:1544-1550 and src/doltlite_rebase.c:1640-1647: bPlanDropped is set only after a successful claim, so a claim-side hard error reaches the pre-claim branch and returns 'rebase failed'. The smallest practical fix is to preserve the original claim error whenever refresh or working-set reload is unsuccessful, return the recovery-failure message, and only report 'no rebase in progress' after a successful durable-state read proves that isRebasing is clear.
  • Why this is likely a bug: The failure is reproduced by a deterministic C regression with an injected claim/refresh fault, and the source contains the exact early-return and error-routing paths that produce the wrong contract. The expected behavior is explicit in the regression oracle: when durable state cannot be read, abort must report recovery failure and must not claim that a peer cleared the rebase. The PR's changed abort claim flow is the direct cause, so a targeted correction to its error decision is appropriate.
Relevant code

src/doltlite_rebase.c:1057-1063

rc = chunkStoreLockAndRefresh(cs);
if( rc!=SQLITE_OK ) return rc;
...
rc = chunkStoreForceRefresh(cs);
if( rc!=SQLITE_OK ) goto claim_done;
rc = doltliteLoadWorkingSet(db, zBranch);
if( rc!=SQLITE_OK ) goto claim_done;

src/doltlite_rebase.c:1404-1447

rc = rebaseClaimActiveEndRetry(db);
...
if( rc!=SQLITE_OK ){
  ...
  if( !stillRebasing ){
    sqlite3_result_error(context, "no rebase in progress", -1);
  }else{
    rebaseResultRecoveryFailure(context, rc);
  }
  return;
}
...
sqlite3_result_text(context, "Interactive rebase aborted", -1, SQLITE_STATIC);

src/doltlite_rebase.c:1544-1550

rc = rebaseClaimActiveEndRetry(db);
if( rc==SQLITE_DONE ){ ... }
if( rc!=SQLITE_OK ) goto abort_err;
bPlanDropped = 1;

test/doltlite_regression_test_c.c:6948-6959

gRegressionFaultCode = 953;
...
res = queryScalarText(db, "SELECT dolt_rebase('--abort')");
...
check("rebase_abort_recovery_failure_is_returned",
      strstr(res, "ERROR: rebase recovery failed")!=0);
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Abort hides refresh failures during recovery**

**What failed:** The recovery regression completed 11 of 12 checks, but rebase_abort_recovery_failure_is_returned failed. The abort operation did not return the required 'ERROR: rebase recovery failed' result.

- **Impact:** When a rebase abort cannot verify durable state, users receive a generic failure instead of a recovery-failure message. They may not know that the rebase state is unverified and needs recovery guidance.
- **Steps to reproduce:**
  1. Create a repository with main and feat branches and start an interactive rebase from feat onto main.
  2. Inject a non-BUSY claim error followed by a force-refresh or durable working-set reload failure.
  3. Run dolt_rebase('--abort').
  4. Check the returned error and the persisted isRebasing state.
- **Stub / mock content:** The test used controlled SQLite fault injection to model a hard claim and refresh failure. No production services, customer data, or application-route mocks were used.
- **Code analysis:** The new claim path in rebaseClaimActiveEnd() calls chunkStoreLockAndRefresh(), chunkStoreForceRefresh(), and doltliteLoadWorkingSet() at src/doltlite_rebase.c:1057-1063. Any non-OK result exits before the durable claim completes. The caller then handles the claim result at src/doltlite_rebase.c:1404-1447, where the PR added a refresh-and-reload check before deciding whether the peer won; if those checks cannot read durable state, the code must retain the original claim error and call rebaseResultRecoveryFailure(), not downgrade the result to a generic rebase failure or infer that no rebase is active. The continue path also demonstrates the unsafe split at src/doltlite_rebase.c:1544-1550 and src/doltlite_rebase.c:1640-1647: bPlanDropped is set only after a successful claim, so a claim-side hard error reaches the pre-claim branch and returns 'rebase failed'. The smallest practical fix is to preserve the original claim error whenever refresh or working-set reload is unsuccessful, return the recovery-failure message, and only report 'no rebase in progress' after a successful durable-state read proves that isRebasing is clear.
- **Why this is likely a bug:** The failure is reproduced by a deterministic C regression with an injected claim/refresh fault, and the source contains the exact early-return and error-routing paths that produce the wrong contract. The expected behavior is explicit in the regression oracle: when durable state cannot be read, abort must report recovery failure and must not claim that a peer cleared the rebase. The PR's changed abort claim flow is the direct cause, so a targeted correction to its error decision is appropriate.

**Relevant code:**

`src/doltlite_rebase.c:1057-1063`

~~~C
rc = chunkStoreLockAndRefresh(cs);
if( rc!=SQLITE_OK ) return rc;
...
rc = chunkStoreForceRefresh(cs);
if( rc!=SQLITE_OK ) goto claim_done;
rc = doltliteLoadWorkingSet(db, zBranch);
if( rc!=SQLITE_OK ) goto claim_done;
~~~

`src/doltlite_rebase.c:1404-1447`

~~~C
rc = rebaseClaimActiveEndRetry(db);
...
if( rc!=SQLITE_OK ){
  ...
  if( !stillRebasing ){
    sqlite3_result_error(context, "no rebase in progress", -1);
  }else{
    rebaseResultRecoveryFailure(context, rc);
  }
  return;
}
...
sqlite3_result_text(context, "Interactive rebase aborted", -1, SQLITE_STATIC);
~~~

`src/doltlite_rebase.c:1544-1550`

~~~C
rc = rebaseClaimActiveEndRetry(db);
if( rc==SQLITE_DONE ){ ... }
if( rc!=SQLITE_OK ) goto abort_err;
bPlanDropped = 1;
~~~

`test/doltlite_regression_test_c.c:6948-6959`

~~~C
gRegressionFaultCode = 953;
...
res = queryScalarText(db, "SELECT dolt_rebase('--abort')");
...
check("rebase_abort_recovery_failure_is_returned",
      strstr(res, "ERROR: rebase recovery failed")!=0);
~~~

Tim added 3 commits August 6, 2026 09:40
Left over after best-effort cleanup stopped using it; fails -Werror
unused-variable on assert-enabled / dead-code gates.
Ito regressions: fault 953 (DROP during claim) was reporting
"no rebase in progress" or silent success because isRebasing was
cleared before DROP failed.

- Drop the plan table before clearing durable isRebasing so a storage
  fault during DROP leaves the rebase flag set and returns recovery
  failed.
- Only report "no rebase in progress" when claim returns SQLITE_DONE
  (successful durable read with isRebasing clear).
- After a successful claim, soft-succeed cleanup on BUSY/LOCKED or a
  missing temp branch (concurrent --continue), but keep hard cleanup
  errors as recovery failed.
Empty commit to restart CI; previous failures were Service Unavailable
while resolving action download info, not product code.
@itoqa

itoqa Bot commented Aug 6, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report9c289df420ac78: 32 test cases ran, 2 new failures ❌, 3 fixed ✅, 26 passing ✅, 1 additional finding ⚠️.

Diff Summary

Coverage spans normal rebase completion and cancellation, reopening and persistence across connections, concurrent continue/abort ownership, branch and repository cleanup, and storage-failure recovery. The broader behavior is generally healthy, but fresh or reopened connections cannot reliably continue a valid rebase, exposing a core workflow failure.

Not safe to merge yet — this PR introduces a high-severity regression in completing valid rebases after a connection change, with a related medium-severity failure for reopened rebases that can discard the expected replayed changes. An unrelated medium-severity missing-session-branch finding is a flag for later, not a driver of this verdict.

Tests run by Ito

View full run

Result State Severity Type Description
❌ New Failure High severity Durable A fresh connection started with a valid interactive rebase plan, but --continue returned an error saying the rebase failed and the branch was restored to its pre-rebase state. The expected finalized refs and post-rebase commit were not produced.
❌ New Failure Medium severity Durable The reopened database showed the saved plan and temporary branch, but continue returned 'rebase failed — branch restored to pre-rebase state'. The temporary state was cleared and a later commit succeeded, yet the expected replayed feature changes were never applied.
❌->✅ Fixed Cleanup The recovery check completed successfully, with all 44 checks passing and no bad recovery outcomes. Genuine storage errors remain distinguishable from a peer that already finished cleanup.
❌->✅ Fixed Contract The fault-injection suite completed all 24 checks successfully. Refresh, restoration, and plan-removal faults were reported with the expected error category instead of being mistaken for a completed rebase.
❌->✅ Fixed Durable A storage failure while ending a rebase returned a recovery error instead of claiming that no rebase was active. The rebase state and plan survived reopening, and a later commit succeeded.
Passing Claim Aborting an active rebase returns a storage error without claiming the rebase or clearing its durable state. The targeted native regression passed all 9 checks.
Passing Claim Claim-related storage failures are reported without clearing the active rebase or leaving the graph lock held. The native fault-injection checks passed all 21 assertions.
Passing Claim The lock contention checks passed. A child process did not keep a released lock, only one contender won promotion, and the related 12-trial race ended with zero bad outcomes and successful follow-up commits.
Passing Claim The available test could not inject lock exhaustion, but source checks show that lock retries are bounded and failed lock refreshes release their resources. A fresh connection is not left blocked by this path.
Passing Claim A working-set reload failure is reported as a recovery failure instead of falsely saying that no rebase is active, and the database lock is released.
Passing Claim Running continue and abort at the same time leaves exactly one action in charge of ending the rebase. The other action exits cleanly, and the repository still accepts a new commit afterward.
Passing Cleanup Deleting the temporary branch before abort cleanup is treated as successful completion. The native concurrency harness completed all 44 checks with zero bad outcomes, and the repository remained usable.
Passing Cleanup Aborting a rebase leaves an independent change on the other branch intact, and both branches remain usable for later work.
Passing Cleanup Reopening after concurrent cleanup did not bring back the deleted temporary branch. The native harness completed all 44 checks with zero bad outcomes, and the repository remained usable.
Passing Cleanup Running continue and abort at the same time leaves one clear winner, avoids a recovery error, and keeps the repository ready for the next commit.
Passing Cleanup Rebase races with garbage collection and branch checkout completed successfully. Concurrent continue and abort also finished without a bad recovery result, and all 44 harness checks passed.
Passing Contract All 12 concurrent rebase trials completed successfully with no recovery errors or failed assertions.
Passing Contract Continuing a valid rebase returned the successful update message, removed the temporary rebase state, and left the branch ready for a new commit.
Passing Contract When continue and abort ran at the same time, one operation finished the rebase and the other stopped without replaying changes. All 12 trials passed, and the repository accepted a later commit.
Passing Contract Abort cleanup keeps a clear recovery error for hard storage failures, while ordinary concurrent completion is treated as a successful abort. The native rebase suite passed 24/24 cases and the concurrent continue-versus-abort harness passed 44/44 checks with no bad outcomes.
Passing Contract Completing or canceling a rebase leaves the saved state consistent after the database is reopened. No stale plan or temporary branch remains, and a new commit can be made.
Passing Contract Aborting the rebase restored the feature branch, kept main as the default branch, and preserved the expected three rows. A new row could then be committed, confirming the repository remained usable.
Passing Contract Running the two terminal actions in either order completed the rebase checks successfully. The second action correctly found no rebase still in progress, and cleanup state remained consistent.
Passing Contract After the concurrent rebase race, the repository stayed usable. The follow-up rebase checks completed all 24 assertions with no failures.
Passing Contract Native recovery checks passed, and the full rebase suite also passed. Abort cleanup returns the expected success message while hard recovery failures remain reported as errors.
Passing Durable The tool rejected the bad plan before replay and kept the active rebase data available for repair.
Passing Durable Concurrent continue and abort handling passed all 56 native checks. The race trials reported zero bad outcomes, so a stale continue did not replay work after the other connection removed the plan.
Passing Durable A replay conflict and an invalid rebase plan both kept the repository state consistent. Retrying after reopening did not replay cleared work, and the repository remained usable.
Passing Durable Reopening an interrupted rebase and continuing without an active rebase kept the saved state, plan, refs, and working data consistent across 33 checks.
Passing Rebase When abort and continue ran at the same time, exactly one operation finished the rebase in every trial. The other operation reported the expected no-rebase result, and the database accepted a later commit.
Passing Rebase The rebase regression suite passed all 24 checks. After one connection finished, stale continue and abort requests did not replay changes or undo the result, and later commits remained usable.
⏸️ Skipped Cleanup Aborting an interactive rebase returned success, left the repository on the expected branch, and removed the rebase plan and temporary branch.
⚠️ Additional Finding Medium severity Claim The abort operation does not reach the cleanup claim when the saved session branch is missing or empty. Instead, it returns a no-rebase-in-progress error, so the expected fallback to main, cleanup of copied branch names, and post-cleanup repository check cannot occur.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟡 Abort rejects a missing session branch
  • Severity: Medium Medium severity
  • Description: The abort operation does not reach the cleanup claim when the saved session branch is missing or empty. Instead, it returns a no-rebase-in-progress error, so the expected fallback to main, cleanup of copied branch names, and post-cleanup repository check cannot occur.
  • Impact: When an active rebase has a missing saved session branch, aborting it can fail and leave the rebase uncleared. The user sees a misleading message and may need to recover the repository manually.
  • Steps to Reproduce:
    1. Start an interactive rebase and leave its durable rebase state active.
    2. Set the persisted session branch to null or an empty value while keeping the rebase active.
    3. Run the rebase abort operation.
    4. Check the result and then inspect the rebase state and branch references after reopening the database.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: src/doltlite_rebase.c:1073-1076 assigns zBranch from doltliteGetSessionBranch(db) and substitutes the literal main only when that value is null or empty. That fallback is the behavior CLAIM-8 is meant to verify. However, src/doltlite_rebase.c:1427-1431 reads the persisted rebase state first and returns the user-facing no-rebase-in-progress error whenever zOrigBranchConst or zReturnBranchConst is null, without calling rebaseClaimActiveEndRetry. Consequently, a null/empty persisted session state can be rejected before the claim function gets a chance to select main. The practical fix is to preserve or reconstruct the required cleanup branch names before this guard, or to allow the claim path to handle the missing session branch while retaining enough copied state for cleanup; do not report no rebase in progress merely because the session branch value is absent while durable isRebasing remains set. The native harness did not create this fixture, so this classification relies on the source-level defect hypothesis and the recorded source evidence.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread src/doltlite_rebase.c
** Drop the plan table before clearing isRebasing so a fault during DROP leaves
** the durable flag set (fault 953 / recovery-failure regressions). Callers must
** copy branch names out of session state before claiming. */
static int rebaseClaimActiveEnd(sqlite3 *db){

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View replay

🆕 New Failure: identified in this diff run

Medium severity Reopened rebase cannot continue

What failed: The reopened database showed the saved plan and temporary branch, but continue returned 'rebase failed — branch restored to pre-rebase state'. The temporary state was cleared and a later commit succeeded, yet the expected replayed feature changes were never applied.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Users who reopen an unfinished rebase cannot continue it, so the saved changes are not replayed. The branch is restored, but users must recover the rebase another way instead of completing the expected workflow.
  • Steps to Reproduce:
    1. Create a main branch and a feature branch with committed changes, then start an interactive rebase from the feature branch onto main.
    2. Close the connection that started the rebase before continuing.
    3. Open a fresh connection and confirm that the saved plan has one row and the temporary rebase branch exists.
    4. Run dolt_rebase('--continue').
    5. Inspect the branches and make a normal commit after the command returns.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The relevant implementation is src/doltlite_rebase.c. The PR adds rebaseClaimActiveEnd at lines 1066-1125: it refreshes and reloads the working set, drops main.dolt_rebase at lines 1095-1097, then clears and persists isRebasing at lines 1112-1119. The continue path reads the plan at lines 1553-1561, then the PR-added claim-before-replay block at lines 1603-1611 calls rebaseClaimActiveEndRetry and marks bPlanDropped before replay starts. Replay and branch finalization then depend on the in-memory plan and branch context through rebaseReplayPlanGroup and doltliteMutateRefs at lines 1613-1647. The recorded fresh-connection run proves that durable metadata was readable before continue, but this path still failed and entered abort_err at lines 1701-1735, which reports the restored-branch error after cleanup. This narrows the defect to the changed claim/replay/finalization interaction rather than missing setup: the claim must not invalidate the durable replay context, or continue must reload that context after claiming. A targeted fix should retain a valid plan/context snapshot through the claim or reopen and validate it before replay, while keeping the one-owner behavior.
  • Why this is likely a bug: This is a real product failure because the public dolt_rebase('--continue') operation rejects a valid, persisted rebase after a normal close-and-reopen. The test did not inject faults, mocks, or unsupported commands: it observed plan_rows=1 and temp_ref=1 before the operation, then the application itself returned the recovery error and restored the branch. The PR explicitly changes the ownership and cleanup ordering used by continue, and those changed lines clear the durable state immediately before replay; the failure is therefore directly tied to the PR's new path. The practical fix is targeted: keep the valid durable plan and branch names available after the ownership claim, or reload them before replay and final ref cleanup, then retain the existing post-claim cleanup behavior.
Relevant code

src/doltlite_rebase.c:1066-1119

static int rebaseClaimActiveEnd(sqlite3 *db){ ... sqlite3_exec(db, "DROP TABLE IF EXISTS main.dolt_rebase", ...); ... doltliteClearSessionRebaseState(db); ... doltliteSaveWorkingSet(db);

src/doltlite_rebase.c:1551-1614

rc = doltliteValidateRebasePlanTable(db, &zPlanErr); ... rc = rebaseReadPlan(db, &aPlan, &nPlan); ... rc = rebaseClaimActiveEndRetry(db); ... bPlanDropped = 1; ... rc = doltliteFlushCatalogToHash(db, &curCat);

src/doltlite_rebase.c:1701-1735

abort_err: ... recoveryRc = rebaseDiscardWorkingBranch(...); ... sqlite3_result_error(context, "rebase failed — branch restored to pre-rebase state", -1);
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Reopened rebase cannot continue**

**What failed:** The reopened database showed the saved plan and temporary branch, but continue returned 'rebase failed — branch restored to pre-rebase state'. The temporary state was cleared and a later commit succeeded, yet the expected replayed feature changes were never applied.

- **Impact:** Users who reopen an unfinished rebase cannot continue it, so the saved changes are not replayed. The branch is restored, but users must recover the rebase another way instead of completing the expected workflow.
- **Steps to reproduce:**
  1. Create a main branch and a feature branch with committed changes, then start an interactive rebase from the feature branch onto main.
  2. Close the connection that started the rebase before continuing.
  3. Open a fresh connection and confirm that the saved plan has one row and the temporary rebase branch exists.
  4. Run dolt_rebase('--continue').
  5. Inspect the branches and make a normal commit after the command returns.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The relevant implementation is src/doltlite_rebase.c. The PR adds rebaseClaimActiveEnd at lines 1066-1125: it refreshes and reloads the working set, drops main.dolt_rebase at lines 1095-1097, then clears and persists isRebasing at lines 1112-1119. The continue path reads the plan at lines 1553-1561, then the PR-added claim-before-replay block at lines 1603-1611 calls rebaseClaimActiveEndRetry and marks bPlanDropped before replay starts. Replay and branch finalization then depend on the in-memory plan and branch context through rebaseReplayPlanGroup and doltliteMutateRefs at lines 1613-1647. The recorded fresh-connection run proves that durable metadata was readable before continue, but this path still failed and entered abort_err at lines 1701-1735, which reports the restored-branch error after cleanup. This narrows the defect to the changed claim/replay/finalization interaction rather than missing setup: the claim must not invalidate the durable replay context, or continue must reload that context after claiming. A targeted fix should retain a valid plan/context snapshot through the claim or reopen and validate it before replay, while keeping the one-owner behavior.
- **Why this is likely a bug:** This is a real product failure because the public dolt_rebase('--continue') operation rejects a valid, persisted rebase after a normal close-and-reopen. The test did not inject faults, mocks, or unsupported commands: it observed plan_rows=1 and temp_ref=1 before the operation, then the application itself returned the recovery error and restored the branch. The PR explicitly changes the ownership and cleanup ordering used by continue, and those changed lines clear the durable state immediately before replay; the failure is therefore directly tied to the PR's new path. The practical fix is targeted: keep the valid durable plan and branch names available after the ownership claim, or reload them before replay and final ref cleanup, then retain the existing post-claim cleanup behavior.

**Relevant code:**

`src/doltlite_rebase.c:1066-1119`

~~~c
static int rebaseClaimActiveEnd(sqlite3 *db){ ... sqlite3_exec(db, "DROP TABLE IF EXISTS main.dolt_rebase", ...); ... doltliteClearSessionRebaseState(db); ... doltliteSaveWorkingSet(db);
~~~

`src/doltlite_rebase.c:1551-1614`

~~~c
rc = doltliteValidateRebasePlanTable(db, &zPlanErr); ... rc = rebaseReadPlan(db, &aPlan, &nPlan); ... rc = rebaseClaimActiveEndRetry(db); ... bPlanDropped = 1; ... rc = doltliteFlushCatalogToHash(db, &curCat);
~~~

`src/doltlite_rebase.c:1701-1735`

~~~c
abort_err: ... recoveryRc = rebaseDiscardWorkingBranch(...); ... sqlite3_result_error(context, "rebase failed — branch restored to pre-rebase state", -1);
~~~

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.

1 participant