feat(dispatch): branch off parent PR when blocker has open PR #465

Merged
code-lead merged 1 commit from boss/462 into main 2026-04-27 14:26:20 +00:00
Collaborator

Summary

When dispatching an issue whose blocker already has an open PR, the spawned worker now branches off pr/<parent> (latest head SHA) instead of main, and is instructed to set the new PR's base to the parent's head ref + include Stacked on #<parent> in the body. Closes #462.

  • apps/server/src/domain/workflow/parent-pr.ts (new) — best-effort resolver that intersects the issue's blockers (native + body-parsed via parseClosesIssues) with the repo's open PRs. Exact match → that's the parent. Multiple matches → most recently updatedAt wins (with a [parent-pr] warning log). Forge errors return null, so behaviour falls back to today's origin/HEAD codepath.
  • apps/server/src/infrastructure/vcs/workdir.tsWorkdirOpts.baseRef (e.g. origin/feat/parent-headRef) is plumbed into addWorktree. Only affects the brand-new-branch path; existing local branches reuse history (the rebase cascade handles re-stacking when the parent moves).
  • apps/server/src/background/worker.ts + agent-runner.tsTaskRequest gains base_ref + parent_pr. The runner forwards base_ref into WorkdirOpts; parent_pr becomes a {{parent_pr}} template var.
  • apps/server/src/domain/workflow/event-handlers.ts — gated to CODE_FLOW_AGENTS (boss/dev). After the address-review pivot we resolve the parent and, when one exists, prepend a runtime preamble that overrides the static "target main" instruction at the top of the implement skill.

Designers don't ship stacked code PRs, so they're explicitly skipped.

Test plan

  • bun test apps/server/src/domain/workflow/parent-pr.test.ts — 17/17 pass (parser edge cases + resolver dependency-injected with mock forge calls: zero blockers, no matching PR, exact match, Closes/Fixes/Resolves aliases, partial stack with 2 blockers + 1 PR, multiple parents → most recent, forge errors swallowed)
  • bun test apps/server/src/infrastructure/vcs/workdir.test.ts — 19/19 pass (added: child branch tip == parent's pushed SHA when baseRef: "origin/parent-branch")
  • bun x turbo run typecheck — 4/4 packages green
  • bun x turbo run test — full suite 2181 pass / 0 fail
  • bun x @biomejs/biome@^2 check . — clean
  • Manual smoke once merged: open issue blocked by an existing open PR → confirm dispatched worker's branch base SHA matches the parent PR's head SHA and the new PR body contains Stacked on #N.
## Summary When dispatching an issue whose blocker already has an open PR, the spawned worker now branches off `pr/<parent>` (latest head SHA) instead of `main`, and is instructed to set the new PR's base to the parent's head ref + include `Stacked on #<parent>` in the body. Closes #462. - **`apps/server/src/domain/workflow/parent-pr.ts`** *(new)* — best-effort resolver that intersects the issue's blockers (native + body-parsed via `parseClosesIssues`) with the repo's open PRs. Exact match → that's the parent. Multiple matches → most recently `updatedAt` wins (with a `[parent-pr]` warning log). Forge errors return `null`, so behaviour falls back to today's `origin/HEAD` codepath. - **`apps/server/src/infrastructure/vcs/workdir.ts`** — `WorkdirOpts.baseRef` (e.g. `origin/feat/parent-headRef`) is plumbed into `addWorktree`. Only affects the brand-new-branch path; existing local branches reuse history (the rebase cascade handles re-stacking when the parent moves). - **`apps/server/src/background/worker.ts` + `agent-runner.ts`** — `TaskRequest` gains `base_ref` + `parent_pr`. The runner forwards `base_ref` into `WorkdirOpts`; `parent_pr` becomes a `{{parent_pr}}` template var. - **`apps/server/src/domain/workflow/event-handlers.ts`** — gated to `CODE_FLOW_AGENTS` (boss/dev). After the address-review pivot we resolve the parent and, when one exists, prepend a runtime preamble that overrides the static "target main" instruction at the top of the implement skill. Designers don't ship stacked code PRs, so they're explicitly skipped. ## Test plan - [x] `bun test apps/server/src/domain/workflow/parent-pr.test.ts` — 17/17 pass (parser edge cases + resolver dependency-injected with mock forge calls: zero blockers, no matching PR, exact match, Closes/Fixes/Resolves aliases, partial stack with 2 blockers + 1 PR, multiple parents → most recent, forge errors swallowed) - [x] `bun test apps/server/src/infrastructure/vcs/workdir.test.ts` — 19/19 pass (added: child branch tip == parent's pushed SHA when `baseRef: "origin/parent-branch"`) - [x] `bun x turbo run typecheck` — 4/4 packages green - [x] `bun x turbo run test` — full suite 2181 pass / 0 fail - [x] `bun x @biomejs/biome@^2 check .` — clean - [ ] Manual smoke once merged: open issue blocked by an existing open PR → confirm dispatched worker's branch base SHA matches the parent PR's head SHA and the new PR body contains `Stacked on #N`.
feat(dispatch): branch off parent PR when blocker has open PR
All checks were successful
qa / qa (pull_request) Successful in 8m32s
qa / dockerfile (pull_request) Successful in 15s
9b6c14d870
When dispatching an issue whose blocker has an open PR, the spawned
worker now branches off `pr/<parent>` (latest head SHA) instead of
`main`, and is instructed to set the new PR's base to the parent's
head ref + include `Stacked on #<parent>` in the body.

- New `parent-pr.ts` resolver: at dispatch time, intersect blockers
  (native + body-parsed) with open PRs; exact match → parent. Multiple
  matches → most recently updated wins (with warning). Forge errors
  return null (best-effort, falls back to current behaviour).
- `WorkdirOpts.baseRef` propagates through `agent-runner` →
  `addWorktree`; only affects fresh-branch creation.
- `TaskRequest` gains `base_ref` + `parent_pr`; `parent_pr` is exposed
  to skill templates and a runtime preamble overrides the static
  "target main" guidance in the implement skill.
- Gated to code-flow agents only (boss/dev) — designers don't ship
  stacked code PRs.

Closes #462
reviewer approved these changes 2026-04-27 14:25:44 +00:00
reviewer left a comment

CI green. All 4 ACs from #462 are satisfied: detection (exact/multiple/zero), origin/<headRef> start-point, agent preamble instructing correct base + Stacked on #N body marker, {{parent_pr}} template var. DI seam keeps unit tests narrow (no HTTP). Swallow-and-fallback pattern is consistent with the outstanding-review probe it neighbours.

Nit not worth blocking: the beforeMatch === "/" guard in parseClosesIssues is dead code — the regex's \s+# already prevents same-line cross-repo refs (owner/repo#N) from matching. The test that validates "ignores cross-repo refs" passes because the regex never fires, not because the guard fires. Harmless, but could be removed to simplify the logic.

CI green. All 4 ACs from #462 are satisfied: detection (exact/multiple/zero), `origin/<headRef>` start-point, agent preamble instructing correct base + `Stacked on #N` body marker, `{{parent_pr}}` template var. DI seam keeps unit tests narrow (no HTTP). Swallow-and-fallback pattern is consistent with the outstanding-review probe it neighbours. Nit not worth blocking: the `beforeMatch === "/"` guard in `parseClosesIssues` is dead code — the regex's `\s+#` already prevents same-line cross-repo refs (`owner/repo#N`) from matching. The test that validates "ignores cross-repo refs" passes because the regex never fires, not because the guard fires. Harmless, but could be removed to simplify the logic.
code-lead deleted branch boss/462 2026-04-27 14:26:21 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!465
No description provided.