PRD: Open GitHub PRs as worktree candidates
Status: Specced — backlog (build later)
Owner: Sessionizer plugin
Related: sessionizer.worktree-open, worktree candidate picker (src/worktree/candidates.ts, src/worktree/flow.ts)
Summary
Extend the existing worktree candidate picker so open GitHub pull requests (including cross-fork heads) appear alongside workspaces, checkouts, local branches, and remote branches. Selecting an open PR materializes a local branch named pr-{n} and opens it as a Herdr worktree workspace (with normal create-time layout bootstrap). Selecting an already-materialized pr-{n} reopens as-is.
Today remote branches only come from refs/remotes/*. Fork PR heads never appear after a normal git fetch origin, so contributor PRs cannot be opened through Sessionizer without manual gh pr checkout / fetch.
Goals
- Browse open PRs for the selected base repo and open one as a worktree workspace.
- Support cross-fork PR heads without adding fork remotes.
- Keep a 1:1 identity: PR
#n ↔ local branch pr-{n} (no duplicate “local + PR” rows for the same checkout).
- Keep
gh a soft dependency: missing/unauthenticated gh → no PR rows; git-only flow unchanged.
- Reuse the existing create/open/bootstrap path (
herdr worktree create / open, layout on new worktrees only).
Non-goals (v1)
- Auto-refresh / reset
pr-{n} to the latest PR tip on reopen.
- Browsing closed/merged PRs (old checkouts remain via local/workspace
pr-{n}).
- Hard dependency on
gh.
- Separate plugin action (e.g.
sessionizer.worktree-pr).
- Config toggle or cache for
gh pr list.
- Non-interactive CLI flag (
--pr 29).
- Fancy PR preview (
gh pr view body/diff) — keep existing candidate preview style.
- Deduping same-repo remote head rows vs PR rows (both may appear).
- Extra git config / branch metadata beyond the
pr-{n} name.
- Pre-fetch of all remotes on every picker open.
User experience
Triggering
Unchanged entry: sessionizer.worktree-open → project/repo picker → candidate picker.
Candidate list order
- Existing worktree workspaces
- Existing git worktree checkouts
- Open PRs (only those not already represented by
pr-{n})
- Local branches
- Remote branches
Open PR row label
open pr #<n> <title> <owner>:<headRefName>
Example:
open pr #29 fix(worktree): gate branch fallback… pperanich:fix/branch-exists-check
Detail/preview can include PR number, intended local branch pr-{n}, and fetch base (pull/{n}/head). Existing metadata/README-style preview is enough for v1.
After materialization (1:1)
| State |
Picker shows |
| Never opened |
One open pr row for #n |
| Opened once |
One existing workspace / checkout / local row for branch pr-{n} |
| Open PR row |
Hidden once pr-{n} is in the seen-branch set |
Selecting the existing row reopens as-is (focus/open path; no layout re-bootstrap; no auto-update to latest tip).
Same-repo PRs vs remotes
A same-repo PR may appear both as:
remote branch origin/<head> → would create local <head>
open pr #n … → creates local pr-{n}
Both are allowed in v1 (different identities).
Esc / new branch
Unchanged: Esc or empty candidate list still prompts for a new branch name.
Behavior detail
Listing PRs
When building candidates for a project:
-
Run existing git/Herdr discovery as today.
-
If gh is available and succeeds, run approximately:
gh pr list --state open --limit 30 --json number,title,headRefName,headRepositoryOwner,author,isDraft,isCrossRepository
(cwd / -R = selected project repo.) Include drafts. Default limit ~30.
-
If gh is missing, non-zero, or unauthenticated: soft silent skip — no PR rows, no hard failure.
-
For each open PR, if local/workspace/checkout already has branch pr-{n}, omit the PR row.
-
Otherwise append a pull-request candidate.
First open (materialize)
On select of an open PR #n when pr-{n} does not yet exist:
-
From the base repo root:
git fetch origin pull/{n}/head:refs/heads/pr-{n}
Equivalent is fine (e.g. fetch to a temp ref then create with --base), as long as the local branch ends up named pr-{n} at the PR head tip.
-
Call existing create path:
herdr worktree create --branch pr-{n} [--base <ref if needed>] …
then normal bootstrap (layout) for new worktrees.
-
Prefer not using gh pr checkout (avoids fighting branch naming and dirtying the main checkout).
gh is used for list only; checkout is git + herdr.
Reopen
If pr-{n} already exists as workspace, checkout, or local branch: existing open/focus/create-recovery paths apply. Do not fetch/reset to latest PR tip in v1.
Failure modes
| Failure |
Behavior |
gh list fails |
Skip PR section silently |
git fetch pull/n/head fails |
Surface error; do not create worktree |
| Create/open herdr errors |
Existing worktree error/recovery behavior |
Implementation sketch
New candidate kind
| {
id: string; // e.g. pr:29
kind: "pull-request";
label: string;
branch: string; // pr-{n}
prNumber: number;
title: string;
headRefName: string;
headOwner: string;
previewPath: string; // project root
}
Discovery
- Extend
discoverWorktreeCandidates / buildWorktreeCandidates (or merge after build) with optional listOpenPullRequests(project).
- Injectable runtime seam (like git runtime) for tests — no live
gh in unit tests.
seenBranches includes pr-{n} so PR rows dedupe once materialized.
Flow
intentFromCandidate for pull-request → ensure fetch + create-branch with branch: pr-{n} and appropriate base (or create branch via fetch refspec then open/create).
- Preserve product rules: existing workspaces reopen as-is; only new worktrees get layout bootstrap (
CONTEXT.md / ADR-0001).
Docs
- README worktree selection table: add row for Open PR → fetch head + create
pr-{n} worktree.
- Note soft
gh dependency for PR rows only.
Acceptance criteria
Test plan
- Injected
listOpenPullRequests: empty, several open, drafts included, pr-{n} already in seenBranches → PR omitted.
intentFromCandidate / flow: first open calls fetch then create with pr-{n}; second path focuses existing workspace.
- Soft skip when PR list runtime throws / returns unavailable.
- Manual: open a cross-fork PR on a real repo; confirm worktree path and layout; reopen; confirm no duplicate PR row.
Design decisions log (from grill)
| Decision |
Choice |
| Job |
Browse open PRs → open as worktree |
| UX placement |
Same candidate picker |
gh dependency |
Soft silent skip |
| Local branch name |
pr-{n} only |
| 1:1 / no duplicate rows |
Hide PR once pr-{n} exists |
| List network |
Always gh pr list when available |
| Open network |
Fetch that PR ref only on first select |
| PR set |
Open + drafts, limit ~30 |
| Reopen |
As-is, no auto-update |
| List order |
After checkouts, before local/remote |
| Same-repo remote + PR |
Allow both |
| Materialize |
git fetch pull/n/head + herdr create |
| Label |
open pr #n title owner:head |
Follow-ups (later)
- Refresh
pr-{n} to latest PR tip when clean / explicit action.
- Cache or debounce
gh pr list.
--pr <n> non-interactive entry.
- Richer PR preview.
- Optional hide of same-repo remote heads that match an open PR.
PRD: Open GitHub PRs as worktree candidates
Status: Specced — backlog (build later)
Owner: Sessionizer plugin
Related:
sessionizer.worktree-open, worktree candidate picker (src/worktree/candidates.ts,src/worktree/flow.ts)Summary
Extend the existing worktree candidate picker so open GitHub pull requests (including cross-fork heads) appear alongside workspaces, checkouts, local branches, and remote branches. Selecting an open PR materializes a local branch named
pr-{n}and opens it as a Herdr worktree workspace (with normal create-time layout bootstrap). Selecting an already-materializedpr-{n}reopens as-is.Today remote branches only come from
refs/remotes/*. Fork PR heads never appear after a normalgit fetch origin, so contributor PRs cannot be opened through Sessionizer without manualgh pr checkout/ fetch.Goals
#n↔ local branchpr-{n}(no duplicate “local + PR” rows for the same checkout).gha soft dependency: missing/unauthenticatedgh→ no PR rows; git-only flow unchanged.herdr worktree create/ open, layout on new worktrees only).Non-goals (v1)
pr-{n}to the latest PR tip on reopen.pr-{n}).gh.sessionizer.worktree-pr).gh pr list.--pr 29).gh pr viewbody/diff) — keep existing candidate preview style.pr-{n}name.User experience
Triggering
Unchanged entry:
sessionizer.worktree-open→ project/repo picker → candidate picker.Candidate list order
pr-{n})Open PR row label
Example:
Detail/preview can include PR number, intended local branch
pr-{n}, and fetch base (pull/{n}/head). Existing metadata/README-style preview is enough for v1.After materialization (1:1)
#npr-{n}pr-{n}is in the seen-branch setSelecting the existing row reopens as-is (focus/open path; no layout re-bootstrap; no auto-update to latest tip).
Same-repo PRs vs remotes
A same-repo PR may appear both as:
remote branch origin/<head>→ would create local<head>open pr #n …→ creates localpr-{n}Both are allowed in v1 (different identities).
Esc / new branch
Unchanged: Esc or empty candidate list still prompts for a new branch name.
Behavior detail
Listing PRs
When building candidates for a project:
Run existing git/Herdr discovery as today.
If
ghis available and succeeds, run approximately:(cwd /
-R= selected project repo.) Include drafts. Default limit ~30.If
ghis missing, non-zero, or unauthenticated: soft silent skip — no PR rows, no hard failure.For each open PR, if local/workspace/checkout already has branch
pr-{n}, omit the PR row.Otherwise append a
pull-requestcandidate.First open (materialize)
On select of an open PR
#nwhenpr-{n}does not yet exist:From the base repo root:
git fetch origin pull/{n}/head:refs/heads/pr-{n}Equivalent is fine (e.g. fetch to a temp ref then create with
--base), as long as the local branch ends up namedpr-{n}at the PR head tip.Call existing create path:
then normal bootstrap (layout) for new worktrees.
Prefer not using
gh pr checkout(avoids fighting branch naming and dirtying the main checkout).ghis used for list only; checkout is git + herdr.Reopen
If
pr-{n}already exists as workspace, checkout, or local branch: existing open/focus/create-recovery paths apply. Do not fetch/reset to latest PR tip in v1.Failure modes
ghlist failsgit fetch pull/n/headfailsImplementation sketch
New candidate kind
Discovery
discoverWorktreeCandidates/buildWorktreeCandidates(or merge after build) with optionallistOpenPullRequests(project).ghin unit tests.seenBranchesincludespr-{n}so PR rows dedupe once materialized.Flow
intentFromCandidateforpull-request→ ensure fetch +create-branchwithbranch: pr-{n}and appropriatebase(or create branch via fetch refspec then open/create).CONTEXT.md/ ADR-0001).Docs
pr-{n}worktree.ghdependency for PR rows only.Acceptance criteria
ghauthenticated, open PRs (including drafts and cross-fork) appear in the worktree candidate list after repo select.gh, worktree flow behaves as today (no crash, no PR rows).pr-{n}, opens a worktree workspace, and applies layout only on first create.pr-{n}, not both PR and local.owner:head.ghdependency.Test plan
listOpenPullRequests: empty, several open, drafts included,pr-{n}already inseenBranches→ PR omitted.intentFromCandidate/ flow: first open calls fetch then create withpr-{n}; second path focuses existing workspace.Design decisions log (from grill)
ghdependencypr-{n}onlypr-{n}existsgh pr listwhen availablegit fetch pull/n/head+ herdr createopen pr #n title owner:headFollow-ups (later)
pr-{n}to latest PR tip when clean / explicit action.gh pr list.--pr <n>non-interactive entry.