Skip to content

Continue an existing Issue branch - #20

Merged
JacobStephens2 merged 5 commits into
mainfrom
issue-6
Sep 25, 2026
Merged

JacobStephens2 merged 5 commits into
mainfrom
issue-6

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

Continuation (ADR-0002): if an Issue branch is already on origin with no PR, or with an open PR, the Run picks it up instead of starting over.

 run
   pre-flight: Origin match, issue open, git identity
-  base = checked-out branch (detached HEAD → error)
-  worktree: new issue-<n> from origin/<base>
-  fresh prompt
+  branch::select                      # git ls-remote + gh pr list --state all
+    highest used Issue branch, on origin, PR absent/open → Continuation
+    none used                                           → Fresh issue-<n>
+    PR merged/closed or branch gone                     → error (numbered branches: #7)
+    local copy of the chosen branch ≠ origin's         → error, nothing created
+  base = open PR's base, else the checked-out branch (note on stderr when they differ)
+  pre-flight on base: exists on origin, local copy (if any) not ahead
+  Fresh        → worktree -b <branch> origin/<base>, fresh prompt
+  Continuation → worktree -B <branch> origin/<branch>, continuation prompt
+                 ("Update PR <url>" form with an open PR, else "Create a pull request")
   session → push → check PR
src/
├── branch.rs     # new: Issue branch selection, Base branch, local-branch check
├── preflight.rs  # Base branch checks split out to run after selection
├── github.rs     # + gh pr list; PrState enum; shared gh JSON helper
├── prompt.rs     # + continuation prompt
├── worktree.rs   # + continue_existing
└── run.rs        # wires it together
tests/
├── continuation.rs   # new
├── fakes/gh          # + pr list (head: prefix search), call recording
└── support/mod.rs    # + origin_has_branch, github_has_pr, launch_checks_out, gh_calls, first_prompt

Tested seams

Evidence

  • Before (main): thirdshift <issue 7> with issue-7 already on origin creates issue-7 fresh from main. The push is rejected (non-fast-forward) and the run exits 1.
    After: cargo test: 12 in tests/continuation.rs + 11 in tests/fresh_run.rs + 10 in tests/preflight.rs, all passing:

    an_issue_branch_with_no_pr_is_continued                       # commits: Continue ← Earlier ← Initial
    without_a_pr_the_continuation_prompt_asks_for_one              # exact create-PR form
    an_issue_branch_with_an_open_pr_is_continued_and_that_pr_is_the_result
    with_an_open_pr_the_continuation_prompt_asks_to_update_it      # "Update PR …/pull/1 …"
    the_open_prs_base_is_the_base_branch_whatever_is_checked_out   # develop checked out → main + stderr note
    without_an_open_pr_the_checked_out_branch_stays_the_base_branch
    an_open_pr_supplies_the_base_branch_even_on_a_detached_head
    a_local_issue_branch_that_differs_from_origin_stops_the_run    # no session, worktree, temp dir or log
    a_local_issue_branch_missing_from_origin_stops_the_run
    a_local_issue_branch_matching_origin_is_continued
    a_failed_runs_branch_is_continued_from_its_failure_commit
    selection_asks_github_once_for_the_pr_history                  # gh calls == [issue view, pr list, pr view]
    another_issues_branch_and_pr_do_not_count_as_this_issues       # issue-70 ≠ issue-7 (fresh_run.rs)
    

    I checked that the local-branch tests aren't vacuous: with the local-branch check disabled, both "stops the run" tests fail.

Merge Danger

Door: two-way

The change adds no stored state or migrations. A Continuation only adds commits and pushes; it never rebases or force-pushes. The one destructive step (worktree add -B, then the existing branch -D at cleanup) runs only after selection has checked that the local Issue branch matches origin's.

Blast Radius: runs

Every Run now makes a gh pr list call before the worktree exists. A gh failure there now stops a run that used to be fresh. A merged or closed PR on issue-<n> now errors instead of starting over on issue-<n> (that case gets numbered branches in #7).

Unaddressed findings

Standards

  • S5, duplicated text between the fresh and continuation prompts: each prompt stays one literal that matches the spec's text line for line, so it can be checked against Spec: thirdshift, turn a GitHub issue into a ready-for-review PR unattended #2 at a glance. The two differ in layout (single vs blank-line separated), so sharing a tail would split both.
  • S8, Worktree::add takes raw git flags (-b/-B): private, two callers, and the flags say exactly what git does. An enum would only rename them.
  • S2 (part), the second match on Selection in run.rs for worktree + prompt: the Base branch logic moved onto Selection (the domain rule from ADR-0002). Building the worktree and prompt stays in run.rs, which is where the Run's steps live.
  • S3 (part), an IssueBranch type: the repeated format! is gone (everything goes through branch_name). A dedicated type would only wrap two private functions used in one module.

Spec

  • P1, the Base branch pre-flight checks (exists on origin, local not ahead): resolved by merging main (Pre-flight checks #4). Pre-flight is split: Origin match, issue open and git identity run first; the Base branch checks run after selection, on the base selection picked, and still before anything is created. A detached HEAD no longer blocks a Continuation whose open PR supplies the Base branch, and a PR base with no local copy skips the "ahead" check.
  • P2, no test counts git ls-remote calls: git is real in the harness, and counting its network calls would mean tracing git internals. Selection makes exactly one ls-remote call by construction; the gh side is asserted.
  • P3, a local issue-<n> with no origin copy is an error: kept on purpose. User story 16 says local-only commits are never destroyed, and the run would otherwise delete that branch at cleanup.
  • P4, a merged/closed PR or a vanished branch is an error: a placeholder until Numbered Issue branches after a merged or closed PR #7 (numbered Issue branches, blocked by this issue). Reusing issue-<n> there would reopen finished work, which ADR-0002 forbids.
  • P5, fork PRs are filtered out (isCrossRepository): a fork's issue-7 is not this repository's Issue branch. The filter costs nothing and avoids a wrong Continuation.
  • P6, the prompt names <base>, and the local <base> may be stale or missing when the PR's base isn't checked out: the prompt wording is the spec's verbatim, and the same staleness applies to fresh runs. Fixing it means either creating or moving the user's local branches or changing the spec's prompt; worth deciding in the spec, not here. origin/<base> is fetched so the agent can use it.
  • P7, gh pr list --search head: relies on GitHub's search index, which can lag: the alternative, listing every PR without --search, grows with the repo's whole PR history. The lag is seconds, and the only case it affects is a PR merged moments before the Run, which matters for Numbered Issue branches after a merged or closed PR #7. Revisit there if needed.

Closes #6

🤖 Generated with Claude Code

JacobStephens2 and others added 5 commits September 25, 2026 04:40
Issue branch selection now reads the Issue branches on origin (one git
ls-remote) and their PRs in any state (one gh pr list). If the
highest-numbered one is on origin with no PR or an open PR, the run
checks it out and gives the agent the continuation prompt, in its
update-PR form when a PR is open. An open PR's base becomes the Base
branch, with a note on stderr when it differs from the checkout. A
local Issue branch that doesn't match its origin copy stops the run
before anything is created.

Numbered branches after a merged or closed PR are left to #7; for now
that case is an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anch

From review: selection now refuses a local Issue branch that differs
from origin, and works out the Base branch itself, so a detached HEAD
only matters when the Base branch has to come from the checkout. PR
state is an enum, gh JSON calls share one helper, and branch names come
from one function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the pre-flight checks (#4) and the README. Pre-flight is split
around Issue branch selection:

- preflight::check (Origin match, issue open, git identity) runs first.
- The Base branch comes from the selection (the open PR's base, else the
  checked-out branch), so a detached HEAD still doesn't block a
  Continuation whose open PR supplies it. The detached-HEAD error keeps
  pre-flight's hint.
- preflight::check_base_branch then checks that base exists on origin and
  that the local copy, if there is one, isn't ahead. A PR's base need not
  exist locally.

Both branches added a gh JSON helper; issue-6's (plain Value, "no pull
requests found" handled by pull_request_for) is kept and issue_is_open
uses it. The gh-call test now expects pre-flight's `issue view` first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in merging the Base branch with a conflict Repair (#8) and the
progress lines (#10).

- run.rs: main's run_session runs the implement session with the prompt
  Issue branch selection picked (fresh or continuation), so a
  Continuation also gets the Base-branch merge and Repair afterwards.
- worktree.rs: the shared Worktree::add prints main's "creating worktree"
  progress line, naming its start ref (origin/<base> fresh,
  origin/<branch> continued). Callers do the fetching.
- prompt.rs, tests/support: both sides' additions kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the Failed run path (#5): failure commit, push, draft PR,
interrupts.

- run.rs: keeps main's run/implement split so any failure after the
  worktree exists goes through failed_run::fail. run() does Issue branch
  selection and creates the fresh or continued worktree and its prompt;
  implement() takes the prompt. The interrupt check still comes right
  before the worktree is created.
- github.rs: PullRequest keeps issue-6's typed fields (number, head,
  PrState) and gains main's is_draft and is_open(); PR_FIELDS asks for
  isDraft. main's mark_ready/convert_to_draft and gh() helper are kept
  alongside issue-6's gh_json.
- tests: both added an origin_has_branch helper. issue-6's
  (branch, from, commits) covers main's single use, so failed_run.rs now
  calls origin_has_branch("develop", "main", &[]). The fake gh supports
  both pr list and pr ready, and the harness passes FAKE_GH_RECORD through
  main's shared command builder.

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.

Continuation of an existing Issue branch

1 participant