Skip to content

Merge run tracer bullet: thirdshift merge <Issue URL> merges a clean PR - #96

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-91
Sep 27, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-91

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

thirdshift merge <Issue URL> (alias --merge) starts a Merge run. It does everything a Run does, then the Self-merge, on exactly the head commit whose CI the Repair loop watched (ADR-0004).

 main(args)
+  "merge" | "--merge" <url>   -> Goal::Merged
+  <url>                       -> Goal::ReadyForReview
+  anything after <url>        -> exit 2 + help
   run(issue, goal)
     implement session          # same prompt in both goals
     push, check PR, mark ready
-    repair_loop()
+    watched = repair_loop()    # now returns the head it saw green/absent
     ensure_pr_ready_and_mergeable()
     interrupt check
+    if goal == Merged
+      gh pr merge <branch> --merge --match-head-commit <watched>
+      (failure -> ordinary Failed run, for now)
-  stderr: "PR <url> is ready for review"
+  stderr: "PR <url> " + goal.outcome()   # "is ready for review" | "is merged"

The fake gh gains pr merge: a real --no-ff merge commit of the head into the base on the test origin, the PR marked MERGED, issues named by closing keywords closed only when the base is origin's default branch, and a refusal when --match-head-commit doesn't match. Any other pr merge flag, such as --auto or --delete-branch, exits 2.

The README documents the merge form, the Self-merge step, the new last stderr line, and the exit-2 rule for trailing arguments.

Evidence

New tests/merge_run.rs and extended tests/command_surface.rs, driven through the binary:

merge <url>            -> exit 0, stdout = PR url, last stderr = "PR <url> is merged",
                          main's tip = merge commit whose 2nd parent = watched head,
                          gh call = pr merge issue-7 --repo acme/widgets --merge --match-head-commit <head>
--merge <url>          -> same
merge into develop     -> merged into develop, issue #7 stays OPEN
head moves during CI   -> exit 1 (Failed run), "Head branch was modified", PR still OPEN
plain <url>            -> no pr merge call, PR open and ready, "is ready for review"
prompts                -> Merge run's implement prompt == Run's
<url> --merge / merge <url> extra -> exit 2, error + help, no session
help                   -> lists "thirdshift merge <Issue URL>", never "--merge"
  • Before: thirdshift merge <url> → exit 2 not a GitHub issue URL: merge. thirdshift <url> --merge silently started a normal Run.
  • After: cargo test: every suite passes (merge_run 7/7, command_surface 8/8). cargo clippy --all-targets is clean.

Merge Danger

Door: two-way

The normal Run path is unchanged except for one thing: an argument after the Issue URL is now rejected with exit 2, where before it was ignored.

Blast Radius: small

Scripts that pass extra arguments after the URL now fail fast. A Merge run merges into the Base branch, which can't be undone, but only when someone asks for it with merge.

Unaddressed findings

Standards

  • Mysterious Name: github::merge should be named for the Self-merge. Skipped: it's a thin gh wrapper named after the gh command, like its siblings mark_ready and convert_to_draft. The Self-merge step lives in run.rs.
  • Primitive Obsession: the head commit is passed as a String. Skipped: this matches the existing worktree.head() and ci::watch(sha). A commit type would be a wider refactor.
  • Parameter count: implement takes 7 params because goal is threaded through. Skipped: the reviewer agreed the shape is defensible, since doing the merge in main would bypass the Failed run path.

Spec

  • Scope creep: the fake gh refuses a merge that conflicts, and no test covers that. Kept: a real GitHub merge would fail the same way, and the later conflict-at-merge-time ticket will use it.
  • Nit: merge is now a boolean flag for every fake gh subcommand. Skipped: no other supported subcommand takes a --merge value.
  • Note: a Base branch that moves between the last check and the merge isn't re-watched. Skipped: --match-head-commit pins the head, as ADR-0004 requires. Merge-time repairs are for later tickets under Merge run: a Run that ends with its PR merged #26.

Closes #91

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits September 27, 2026 12:40
`thirdshift merge <Issue URL>` (alias `--merge`) runs everything a Run
does, then the Self-merge: `gh pr merge --merge --match-head-commit`
on the head commit whose CI the Repair loop watched. A merge that fails
is an ordinary Failed run for now. Anything after the Issue URL is now
an argument error.

The fake gh gains `pr merge`: a real merge commit on the test origin,
closing linked issues only when the base is the default branch, and a
refusal when the head doesn't match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the Run's mode after its goal (ready for review or merged), keeping
the outcome text beside it; mention merged PRs in the help's first line;
and deduplicate the fake gh's git calls and the merge assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit a6885f7 into main Sep 27, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-91 branch September 27, 2026 12:54
@JacobStephens2 JacobStephens2 mentioned this pull request Sep 27, 2026
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.

Merge run tracer bullet: thirdshift merge <Issue URL> merges a clean PR

1 participant