Skip to content

Keep the PR mergeable: merge the Base branch and conflict Repair - #17

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-8
Sep 25, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-8

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

After the PR is confirmed, thirdshift merges the Base branch into the Issue branch. It never rebases. A conflicting merge goes to a conflict Repair session.

 run
   implement session                       (…-implement.jsonl)
   push; check PR (open, targets base)
+  fetch origin <base>; merge --no-edit --ff origin/<base>
+    clean / nothing to merge → (fall through)
+    conflict → leave merge in progress
+      conflict Repair session             (…-repair-1.jsonl, same timestamp)
+      fail unless the merge was finished and origin/<base> is an ancestor of HEAD
+  push
   print PR URL
  • Worktree gains merge_base_branch, ensure_base_branch_merged and push. Git gains succeeds, for commands whose exit status is the answer.
  • The conflict Repair prompt follows the spec word for word, and the session runs with the same plugin and flags.
  • Test harness changes:
    • The fake claude can run a different script per session (claude-script.sh.<n>), and it records whether a merge is in progress.
    • The test origin now sets receive.denyNonFastForwards, so a rebase or force-push fails any scenario.

Out of scope: repeating the merge-then-CI loop, CI watching and the Repair cap belong to #9. The Failed run path (failure commit, draft demotion) belongs to #5.

Evidence

tests/mergeable.rs covers these scenarios through the CLI:

base advanced, no conflict       → origin issue-7 = merge(Add feature, main); 1 session
base unchanged                   → origin issue-7 = [Add feature, Initial commit]; 1 session
base conflicts on feature.txt    → session 2 gets the conflict Repair prompt with merging=true;
                                   its resolution is pushed as a fast-forward
logs                             → <prefix>implement.jsonl and <prefix>repair-1.jsonl
Repair leaves the merge unfinished → non-zero exit, "merge of origin/main is still in progress"
Repair runs `git merge --abort`  → non-zero exit, "origin/main is not merged into issue-7"
  • Before: on main without the implementation, 4 of the first 5 scenarios failed. Only "base unchanged" passed. The abort case would have exited 0.
  • After: cargo test passes all 14 tests, and cargo clippy --all-targets -- -D warnings and cargo fmt --check are clean.

Merge Danger

Door: two-way

The only thing this pushes to origin is a merge commit on the Issue branch, and that is always a fast-forward.

Blast Radius: runs

Every Run now fetches and merges the Base branch before it reports success. A Run whose conflict Repair fails now exits non-zero. Until #5 lands, that exit leaves the PR as it is.

Unaddressed findings

Standards

  • Shallow interface / locality: the merge → Repair → check → push sequence still lives in run.rs, not in one deeper module. I agree with the reviewer's own advice to wait for CI watching, CI-fix Repair and the Repair cap #9's loop and CI-fix Repair, because deepening now would be speculative.
  • Primitive Obsession (session kinds as strings): "repair-1" stays a literal. CI watching, CI-fix Repair and the Repair cap #9 adds the Repair counter, and that is the point where a SessionKind type pays for itself.
  • Duplicated MERGE_HEAD check in the fake claude: left as is on purpose. The fake observes the merge state independently of the code under test.

Spec

  • Scope creep: the unfinished-merge guard fails the run: kept and extended. Pushing after a Repair that didn't merge would report a conflicting PR as success. The Failed run path #5 will route this failure through the Failed run path, which aborts the merge.
  • Harness changes affect every scenario: kept. A fast-forward-only origin is the direct evidence for "No rebase or force-push ever reaches origin", and all existing tests still pass.

Closes #8

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits September 25, 2026 04:39
Once the PR is confirmed, thirdshift fetches origin/<base> and merges it
into the Issue branch, never rebasing, then pushes. A conflicting merge is
left in progress and handed to a conflict Repair session (logged as
…-repair-1.jsonl with the run's timestamp); the run fails if that session
leaves the merge unfinished.

The test origin now rejects non-fast-forward pushes, so no rebase or
force-push can reach it unnoticed, and the fake claude can run a different
script per session and records whether a merge is in progress.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Repair that aborts the merge left no MERGE_HEAD and passed as a
success with the PR still conflicting; now the run also requires
origin/<base> to be an ancestor of the Issue branch. Merging passes --ff
so a user's merge.ff=only can't break a clean merge.

From review: rename merge_base to merge_base_branch (it isn't git
merge-base), share Git's command setup, run sessions through one closure,
and push via Worktree::push. The clean-merge test now pushes first so the
merge push must be a fast-forward.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit d446431 into main Sep 25, 2026
1 check passed
@JacobStephens2
JacobStephens2 deleted the issue-8 branch September 25, 2026 11:14
JacobStephens2 added a commit that referenced this pull request Sep 25, 2026
Resolve src/main.rs and src/run.rs against the pre-flight checks (#15/#16)
and the Base branch merge and conflict Repair (#17). Keep main's run
structure and carry the progress lines into it: the session log line
moves into run_session, the push line into Worktree::push, and the newly
built steps (merging the Base branch, starting a Repair) print their
lines as issue #10 asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JacobStephens2 added a commit that referenced this pull request Sep 25, 2026
Brings in pre-flight checks (#16), the README (#15) and merging the Base
branch with conflict Repair (#17) on top of the Failed run path.

- run.rs: preflight::check replaces the inline origin check; the Repair
  session and final push sit inside implement(), so a failed Repair goes
  down the Failed run path. The session log reported on failure is the
  most recent one (repair-1 if it ran).
- main.rs: parse the Issue URL up front (exit 2) as main does, then
  install the interrupt handler and report a FailedRun.
- github.rs: main's gh_json/string_field helpers, plus isDraft, is_open,
  mark_ready and convert_to_draft.
- failed_run.rs: reuses Worktree::merge_in_progress.
- tests/mergeable.rs: a failed Repair is now a Failed run, so it prints
  the open PR's URL and sends the PR back to draft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Keeping the PR mergeable: merge the Base branch and conflict Repair

1 participant