Skip to content

check: --repair reuses the intact pack check results within --max-age, refs #10026 - #10464

Open
mr-raj12 wants to merge 12 commits into
borgbackup:masterfrom
mr-raj12:check-repair-max-age-10026
Open

mr-raj12 wants to merge 12 commits into
borgbackup:masterfrom
mr-raj12:check-repair-max-age-10026

Conversation

@mr-raj12

Copy link
Copy Markdown
Contributor

Description

Point 5 of #10026: use the cache/checked-packs records during repair.

--repair rejected --max-age and re-hashed every pack, so a repair on a large repository paid full verification cost even right after a check. It now reuses intact records the same way a normal check does.

  • --repair + --max-age is allowed. --repair + --max-duration and --archives-only + --max-age stay rejected.
  • A pack recorded corrupt is never skipped: the skip requires an intact record. The salvage therefore still acts on a result from this run for every pack it touches.
  • --repository-only --repair with a corrupt index ignores --max-age and logs it. It rebuilds the index from the packs it verified in that run, so a skipped pack would feed the rebuild unverified. A full --repair is unaffected - the archives phase rebuilds from every pack.
  • The salvage gate becomes pack_files + pack_skipped == len(pack_infos), so a skipped pack does not block the salvage; an interrupted or time-boxed loop still does.

This is option 2 of the three in #10026 (comment), narrowed to --repository-only: after #10445 the index rebuild inside check() runs only on a repository-only repair, so a full repair can keep the reuse.

Stacked on #10455.

Checklist

  • PR is against master (or maintenance branch if only applicable there)
  • New code has tests and docs where appropriate

@codecov

codecov Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.78%. Comparing base (8274470) to head (fb72df4).
⚠️ Report is 13 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10464      +/-   ##
==========================================
+ Coverage   88.73%   88.78%   +0.04%     
==========================================
  Files         103      103              
  Lines       19387    19483      +96     
  Branches     3023     3045      +22     
==========================================
+ Hits        17204    17298      +94     
- Misses       1514     1515       +1     
- Partials      669      670       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@mr-raj12
mr-raj12 force-pushed the check-repair-max-age-10026 branch from cdcd518 to 0ff265e Compare September 29, 2026 19:02
@mr-raj12

Copy link
Copy Markdown
Contributor Author

Top of the stack, merge order #10443 → #10445 → #10455 → #10464, so this one goes in last of the four. Green, and ready once the review is.

@ThomasWaldmann ThomasWaldmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! #10443, #10445 and #10455 are merged now, so what remains here is fb72df4 only; it merges cleanly into current master.

The interaction with the merged salvage and the single index rebuild looks right to me: packs recorded corrupt are never skipped, so the salvage always works on a result of this run, and pack_files + pack_skipped == len(pack_infos) still blocks the salvage after an interrupted loop. A full --repair with a corrupt index leaves the rebuild to the archives phase, which reads every pack anyway.

Bug: a repository-only repair with --max-age can not fix an index fragment that fails the authentication

The max_age = 0 reset is in the if index_errors and repo_only: branch (repository.py:1622), which runs before the index cross-check. But the cross-check can still find an index error: a fragment that matches its name, but fails the authentication of the key's envelope or does not deserialize (CorruptChunkIndexFragment, index_errors += 1 at repository.py:1664). Then max_age stays active, recently checked packs are skipped, pack_files == len(pack_infos) is false and the rebuild never runs. The comment at repository.py:1761-1762 ("max_age was set to 0 above for exactly this path, so no pack was skipped") does not hold in that case.

Repro, based on test_check_repairs_index_fragment_failing_authentication (plaintext fragment), with a plain check() first, so the packs get recorded intact:

assert repository.check(repair=False) is False  # records the packs intact
repository.check(repair=True, repo_only=True, validate=accept_all, max_age=3600)
Checked 2 index files (1 errors) and 0 packs (0 errors). Reused 1 recent pack check result(s).
Finished full repository check, index still corrupt.

The check returns False and the bad fragment is still in index/. Without max_age the same repair rebuilds the index and succeeds. Every rerun within the --max-age window fails the same way, and nothing hints at --max-age being the reason.

Suggestion: do the reset (and its log line) after the cross-check, conditioned on repair and repo_only and index_errors, and add a test for this fragment case (the new test only covers the store hash case via rot_index). A full --repair is not affected, the archives phase rebuilds there.

Docs that this PR makes stale

  • check_cmd.py:311, repair epilog item 1: "repair mode verifies every pack" is no longer true with --max-age.
  • repository.py:1490, check() docstring: "With repair=True, every pack is verified, then ..." contradicts the new max_age paragraph further down.

docs/internals/data-structures.rst (checked-packs) is still correct.

This branch has not been deployed

No deployments
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