Skip to content

docs(research): rr-cache is portable; replay depends on conflicted-hunk drift (#367) - #413

Open
tucktuck101 wants to merge 2 commits into
launchpadfrom
research/367-rerere-empirical
Open

docs(research): rr-cache is portable; replay depends on conflicted-hunk drift (#367)#413
tucktuck101 wants to merge 2 commits into
launchpadfrom
research/367-rerere-empirical

Conversation

@tucktuck101

Copy link
Copy Markdown
Collaborator

Summary

Adds one research document reporting five experiments on git rerere against the fork's real lefthook.yml conflict. The rr-cache is portable, so every remedy #300 considers is viable. The fragility is real with a window of about five lines. When it declines it is loud; the genuinely silent hazard is a resolution recorded wrongly the first time, which reapplies with no warning — which corrects both ADR-0021's stated mechanism and my own earlier answer on #362. And rerere never turns a conflicted merge into a clean one, which matters for success criterion 2.

Related issue

Closes #367

Issue type

Task


Agent provenance

Field Value
Harness / provider Claude Code
Model claude-opus-5
Session reference N/A - harness exposes no run id
Initiating human @tucktuck101

Objective

Add launchpad/Research/367-rerere-portability-and-fragility.md recording whether an rr-cache replays across clones and how fragile the match really is, established by experiment.

Impacted components

launchpad/Research/367-rerere-portability-and-fragility.md

Approach and rejected alternatives

Used the real three versions of lefthook.yml rather than a synthetic conflict, so the fragility result describes the file the fork actually argues about — one whose upstream side is a 66-line rewrite.

Tested the fragility at two distances rather than one, because "does context drift break it" is not a yes/no question. Then, having found that upstream drift makes the match decline, went looking for the case that actually fires silently — which meant deliberately recording a wrong resolution. That experiment is the one that changes what the records should say, and I would not have run it if I had stopped at confirming the documented behaviour.

Rejected: trusting git-rerere(1) and stopping. The documentation describes a three-way replay but does not say how much drift defeats it, whether the cache is portable, or what a wrong resolution does on reapplication. All three are load-bearing for #300 and all three needed running.

Rejected: leaving my own #362 claim standing. I wrote there that the silent hazard was rerere firing stale when upstream's intent changed underneath. Experiment 4b shows that cannot happen — drift changes the preimage and the match declines. The document says so plainly rather than quietly moving on.

Rejected: bisecting the fragility window. Two distances establish that the window is narrow and that ~5 lines defeats it. Pinning the exact threshold would be more precise without changing any decision, and the document reports "about five lines" as an upper bound rather than implying a measurement it did not make.

Verification

Command run:

# repoA: base -> ours (fork's one-line redirect) on master; base -> theirs (upstream 66-line rewrite)
git config rerere.enabled true; git config rerere.autoUpdate false
git merge --no-commit theirs          # 1: record
# resolve by hand, commit
git reset --hard HEAD~1 && git merge --no-commit theirs   # 2: replay, same clone

git clone repoA repoB                 # 3: fresh clone, empty cache
git merge --no-commit theirs
cp -R ../repoA/.git/rr-cache/. .git/rr-cache/
git merge --no-commit theirs          # 4: portability

# 5a: upstream edits a comment ~70 lines from the hunk; 5b: ~5 lines from it
git merge --no-commit theirs

# repoC: record a deliberately WRONG resolution (fork position dropped), then replay
git reset --hard HEAD~1 && git merge --no-commit theirs

Raw output:

$ git merge --no-commit theirs                      # first time
Recorded preimage for 'lefthook.yml'
Automatic merge failed; fix conflicts and then commit the result.
$ git commit -m "merge upstream, resolved"
Recorded resolution for 'lefthook.yml'.
$ find .git/rr-cache -type f | sed 's|.*rr-cache/||'
e8ed5ba8467bd3f9adbcaaa9df6d6f4498bdcfbc/preimage
e8ed5ba8467bd3f9adbcaaa9df6d6f4498bdcfbc/postimage

$ git reset --hard HEAD~1 && git merge --no-commit theirs     # same clone
CONFLICT (content): Merge conflict in lefthook.yml
Resolved 'lefthook.yml' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
--- fork position present without hand-resolution? 2
--- conflict markers left? 0

$ ls .git/rr-cache | wc -l                                    # fresh clone
0
$ git merge --no-commit theirs
Recorded preimage for 'lefthook.yml'
--- markers present (no cache)? 1

$ cp -R ../repoA/.git/rr-cache/. .git/rr-cache/ && git merge --no-commit theirs
Resolved 'lefthook.yml' using previous resolution.
--- fork position present? 2
--- markers left? 0

$ # 5a: upstream edit far from the hunk
Resolved 'lefthook.yml' using previous resolution.
--- replayed? markers=0 forkpos=2

$ # 5b: upstream edit ~5 lines from the hunk
Recorded preimage for 'lefthook.yml'
--- replayed? markers=1 forkpos=2

$ # wrong resolution recorded, then replayed
--- fork position after the wrong resolution: 0
Resolved 'lefthook.yml' using previous resolution.
--- fork position: 0
--- conflict markers: 0

Every replay, including the correct ones, still ended Automatic merge failed; fix conflicts and then commit the result.

  • Tests or checks were run and the raw output is pasted above
  • The diff is confined to the scope of the linked issue
  • No secrets, keys, tokens or hostnames were added to tracked files

Not verified

I tested one file and one conflict shape. rerere's behaviour with multiple conflicts in a single file — where practitioner accounts say it becomes stricter and may re-ask for the whole set — was not tested, and the fork's runtime.rs conflict is exactly the case that would exercise it. I did not test the conflict-marker-size interaction git-rerere(1) warns about for files containing marker-like lines. I did not bisect the fragility window: I measured at ~70 lines and ~5 lines, so "about five lines" is an upper bound on what still fails, not a threshold — the real boundary lies between 5 and 70 and is presumably git's diff context, which I did not confirm. I tested portability by direct filesystem copy, not through actions/cache or a git branch; those transports reduce to the same mechanism but I have not run them. I did not test whether a postimage recorded on one platform replays on another. Nothing was run in CI. I did not run just ci; the diff is one markdown file.

Security implications

None from the diff. One finding is a real trust consideration for #300 and is stated in the document: a shared rr-cache shares mistakes. A wrongly-recorded resolution reapplies with no warning and no marker, so committing one to a shared cache branch means it is applied silently on every contributor machine and in CI. That makes the sharing decision a trust decision as much as a transport one, which #300 currently frames only as transport. It also supports one concrete configuration position: with rerere.autoUpdate false a replayed resolution sits unstaged and git status shows it; with it true, a silently-wrong replay is staged and one commit from the branch.

Escalations

Two corrections are owed and one of them is mine. ADR-0021 says the replay "silently stops firing" on drift — it stops firing loudly, with markers and a stopped merge. And on #362 I said the silent hazard was firing stale when upstream's intent changed; experiment 4b shows that cannot happen, because drift changes the preimage and the match declines. The real silent hazard is a resolution recorded wrongly the first time. The record's conclusion is right in both cases; the fork should not carry two wrong mechanisms in support of it. I have edited neither record.

A configuration position for #300, raised not decided: do not enable rerere.autoUpdate for drop merges.

rerere should probably not be assigned to lefthook.yml or runtime.rs in #294's mechanism column. Those are the two rows where upstream rewrites or restructures, so a stored resolution is least likely to survive and most likely to be wrong if it does — which is the worst combination given experiment 5.

One consequence for success criterion 2 that nobody has stated. rerere never turns a conflicted merge into a clean one; every replay above still exited "Automatic merge failed". A drop whose only conflicts are rerere-replayable is not an unattended clean merge — it is a conflicted merge with the answers pre-filled. That bears on #296 and on how SC2 is judged.

@tucktuck101

Copy link
Copy Markdown
Collaborator Author

Updated for the handbook source-material contract

Force-pushed an amended commit bringing this document into line with two conventions introduced after it was written, since it is unmerged and retrofitting after merge is the expensive case:

1. Every reference pinned to a full 40-character SHA. Fork-side claims cite launchpad-26/buzz at 5d76799d6e44f2f76aa7bd78c5343d339af98f63; upstream-side claims cite block/buzz at 025425591ed67518a63870316f1473ffd02dd520. The SHAs are stated once near the top and the individual references link to them, so the handbook's staleness detection can read the pins.

Two judgement calls made while doing it, flagged so a reviewer can overrule:

  • Paths inside fenced blocks are left unlinked, deliberately. Those are command output; linking them would misrepresent what the command printed. Each document now says so where the pins are declared.
  • A filename that is not a file in this repository is named, not linked. .github/pull.yml in the task: assess existing tooling for vendor-drop automation and drop-report computation #368 document is wei/pull's own config filename; dev-dependencies is a cargo concept. Neither is a citation, and the rule catching them is the rule working.

2. Recommendations separated from evidence and attributed. Each ## What this means for #273 section now opens with an explicit note that it is my recommendation as the author, carries no source reference, and that the evidence is above while the judgement is mine. Where a document contains something I authored rather than found — the CODEOWNERS pattern set in #369 — that is labelled in place as a proposal rather than as repository content.

No finding, figure or caveat changed. The diff is pins, section labels, and one external link moved off blob/master onto a commit SHA.

AI agent (Claude Opus 5) on behalf of @tucktuck101, 2026-08-22.

…solutions reapply silently (#367)

Signed-off-by: tucktuck101 <jeffreytaylorrobertson@gmail.com>
@tucktuck101
tucktuck101 force-pushed the research/367-rerere-empirical branch from 3c59da9 to 75edc27 Compare August 21, 2026 22:57
@tucktuck101

Copy link
Copy Markdown
Collaborator Author

Revised for the fork's horizon (#357)

Force-pushed an amended commit adding a ## Revised for the fork's horizon (#357) section, after @tucktuck101 decided on 2026-08-22 that the fork has no expected lifetime beyond the cohort project, nobody owns upstream adoption after it ends, and the hard end is 2026-09-17.

No evidence, figure or caveat changed. Every measurement and quotation stands exactly as reviewed. What changed is the recommendations — which are now explicitly marked as mine, so the revision is visible rather than a silent rewrite.

I added the section rather than editing the original recommendations in place, so anyone who already read this document can see what moved and why.

Where a recommendation of mine was wrong under the real horizon, I have said so and withdrawn it rather than softening it. The reversals are named in the section.

AI agent (Claude Opus 5) on behalf of @tucktuck101, 2026-08-22.

@serina-mcfall serina-mcfall left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Blocker — the universal negative in §5 is refuted by experiment

The note states:

Upstream drift produces a decline, never a stale fire.

and

Experiment 4b shows that specific mechanism cannot happen.

I ran a controlled rerere experiment in a disposable repository to check this, because the claim is load-bearing: it is the basis on which the note retracts its own #362 position and tells the fork to correct ADR-0021's recorded reasoning. Two records are about to be rewritten around this mechanism.

Setup: 100-line file, fork and upstream both edit line 50, rerere.enabled=true, rerere.autoUpdate=false. A resolution is recorded that keeps the fork's line and deliberately drops upstream's. Upstream then edits line 5 — forty-five lines away from the conflict — and the merge is re-run.

conflict present: 1
Recorded resolution for 'f.txt'.
RECORDED: fork wins, upstream's line deliberately dropped
=== now merge the DRIFTED upstream ===
CONFLICT (content): Merge conflict in f.txt
Resolved 'f.txt' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
markers left: 0
upstream line 50 present: 0
fork line 50 present: 1

Upstream's side changed, and the stored resolution replayed silently — no warning, no decline, and upstream's content dropped. That is precisely a stale fire produced by upstream drift.

The correct statement is narrower, and it is still a useful one: rerere's conflict ID is computed from the conflicted hunk, so drift inside the hunk changes the preimage and declines, while drift outside it replays silently regardless of distance.

Why this is a Blocker rather than a note

The claim is not a detail — it is the retraction. Correcting one wrong mechanism in ADR-0021 with a different wrong mechanism leaves the ledger no better off, and this one is harder to catch because it is stated as a proven negative.

High — "the window is about five lines" does not hold, and the caveat sits below the conclusion

Finding table row 4b records "upstream edits a line ~5 lines from the hunk → does not replay", and §4 is titled "Fragility: real, and the window is about five lines."

In my run a replay occurred at 45 lines. The reviewing agent that first raised this also observed replays at 5 and 3 lines. I cannot refute your specific lefthook.yml observation — a YAML conflict hunk may genuinely be wide enough that a 5-line-distant edit lands inside it — but that is the point: line distance is not the governing variable, hunk membership is, so a distance-based window will mislead whoever prices the risk next.

The caveat you do give ("I did not bisect it, so 'about five lines' is an upper bound … the real boundary is somewhere between 5 and 70") appears in Confidence and limits, after the summary, the Finding table, and two recommendation sections that have already priced the window as real. It also understates the residual uncertainty — a replay at 3 lines falls outside the 5-to-70 band it offers.

What is right, and it is the majority of the note

Five load-bearing claims reproduced, including both that the note treats as decisive:

  • rr-cache is portable across clones. Fresh clone with an empty cache leaves the conflict standing; copying rr-cache in produces Resolved 'f.txt' using previous resolution. This is the result #300 needs, and it is solid.
  • A wrong resolution reapplies silently — byte-identical output to a correct replay. Confirmed.
  • rerere never turns a conflicted merge into a clean one — every merge in my run exited non-zero with Automatic merge failed.
  • With autoUpdate=false the path is not staged — confirmed UU in status.
  • An edit far from the hunk still replays.

The structural discipline here is good: the note pins its refs, pastes real output, and separates what it observed from what it reasoned. The defect is confined to one inferred mechanism.


Reviewed at head 75edc2734. The rerere experiment above was run by me in a disposable repository, not taken from the reviewing agent's report.

🤖 Review drafted by Claude Code (claude-opus-5) for @serina-mcfall.

Signed-off-by: tucktuck101 <jeffreytaylorrobertson@gmail.com>
@tucktuck101 tucktuck101 changed the title docs(research): rr-cache is portable; drift declines loudly, wrong resolutions reapply silently (#367) docs(research): rr-cache is portable; replay depends on conflicted-hunk drift (#367) Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

by:agent Filed or authored by an AI agent, not a human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

task: find out whether a rerere cache replays across clones, and how fragile the match really is

2 participants