Skip to content

fix(agent-manager): do not inherit a merged PR on a reused branch name - #14145

Merged
marius-kilocode merged 4 commits into
mainfrom
fix-pr-batching-logic-errors
Sep 15, 2026
Merged

marius-kilocode merged 4 commits into
mainfrom
fix-pr-batching-logic-errors

Conversation

@marius-kilocode

Copy link
Copy Markdown
Collaborator

What Problem This Solves

A new Agent Manager worktree can display an old merged pull request that it never belonged to, and merge or review actions on that PR fail with The pull request is no longer open.

Agent Manager derives a worktree branch from the session title, so a repeated title recreates the branch name of an already merged PR. gh's finder, and the batched lookup that mirrors it, resolve a PR by head ref name and return the newest merged or closed PR of that name. The worktree therefore inherited a merged PR whose commits are not in its history.

Concretely, PR #12726 (fix(cli): speed up local recall searches, merged 2026-08-03) used branch optimize-kilo-local-recall-performance. Running the same task title again created a worktree with the identical branch name, and the card showed #12726.

Why This Change Was Made

The branch name is the only key GitHub has, so the lookup itself is correct. The mistake is attributing a non-open PR to a checkout that does not contain it. A merged or closed PR is now kept only when the checkout can contain it:

  • open and draft PRs are always kept, with no git calls
  • HEAD equals the PR head
  • the PR head is reachable from HEAD and the merge commit is not, which covers a merged PR with local commits on top

It is dropped when the PR head is not reachable from HEAD (squash or rebase merges, closed PRs, a recreated branch name), or when the PR's merge commit is reachable (the branch was cut from the base after the merge).

The same guard runs for both the batched result and the legacy gh pr view result, because both had the same attribution. The batched GraphQL query gains one scalar field (mergeCommit { oid }) and gh pr view --json gains the same field, so there is no extra request, no extra rate-limit cost of note, and no lookup at worktree creation. For open PRs the guard adds no git work at all.

Branch name deduplication at creation was deliberately not extended. WorktreeManager.availableBranch already suffixes names that exist in refs/heads or as a worktree directory, but the old branch was deleted locally and on the remote, so only GitHub still knew the name. A gh check at creation would add a network round trip and a dependency to worktree creation, and would only cover that one collision source.

User Impact

A worktree that only reuses a merged PR branch name no longer shows that PR. Worktrees that genuinely sit on a merged PR keep the merged badge, including PR review checkouts and PRs checked out through refs/pull/N/head, and open PRs are unaffected.

Within a running extension session, a card that already shows the wrong PR keeps it until the next reload (retainPRStatus intentionally keeps a known PR when an empty result arrives for the same branch). New worktrees are correct immediately.

Evidence

Agent-executed:

  • bun test on am-pr-utils, am-pr-status-bridge, am-pr-batch, am-pr-seed, agent-manager-arch, gh from packages/kilo-vscode: 321 pass, 0 fail. New coverage: 7 unit cases for the guard, mergeCommit parsing, and a batched full-sync case for the recreated branch name.
  • bun run lint and bun run knip in packages/kilo-vscode: clean. parsePRResult optional OID fields were extracted into a helper to stay under the complexity limit.
  • Live check against the reported worktree (optimize-kilo-local-recall-performance, HEAD 2d9c6eda1b7), with the guard fed real gh pr view output and real git history: fix(cli): speed up local recall searches #12726 (squash merge, head 1fb15ce) is rejected, and PR docs(kilo-docs): consolidate older docs-sync rollovers #14135 (merge commit PR whose merge commit is the worktree HEAD) is rejected.
  • bun run typecheck in packages/kilo-vscode reports 216 errors from a stale SDK/dependency build in this worktree (KiloClient.v2, missing @opencode-ai/core/kilocode/zero-id). The same count and the same errors occur without this change, and none are in the touched files.

How to Test

Agent-executed manual verification, using the real worktree and live GitHub data (no writes to the repository):

  1. In a worktree, run gh pr view <branch> --json state,headRefOid,mergeCommit for a branch name that matches an old merged PR. It returns the merged PR.
  2. Run git merge-base --is-ancestor <pr head> HEAD in that worktree. It exits non-zero, so the PR is not in the checkout.
  3. The worktree now shows no PR badge instead of the merged PR.

Reviewer steps:

  1. Create an Agent Manager worktree whose session title matches an earlier merged PR's session title, so the generated branch name matches that PR's head ref.
  2. Confirm the worktree card shows no PR.
  3. Create a worktree from a branch that is at a merged PR's head (or use the PR import flow) and confirm the merged badge still appears for that PR.

Agent Manager derives a worktree branch from the session title, so a
repeated title recreates the branch name of an already merged PR. gh's
finder, and the batched lookup that mirrors it, return that merged PR by
head ref name, so the new worktree showed the old PR, and merge or review
actions failed with "The pull request is no longer open."

Keep a merged or closed PR only when the checkout contains it: its head
is the local HEAD, or its head is reachable from HEAD while the merge
into the base is not. Open and draft PRs are unaffected and need no git
calls. This also covers the legacy gh pr view path, which had the same
attribution.

Adds mergeCommit to the batched query fields and to gh pr view --json, so
no extra request is needed.
Comment thread packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts
Comment thread packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts
Comment thread packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts
@kilo-code-bot

kilo-code-bot Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3

Fix these issues in Kilo Cloud

Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 350 A guard rejection is forwarded as plain pr: null, so retainPRStatus keeps an already-displayed wrong PR (and its failing actions) until reload; empty()'s :none hash prevents any later retry.
packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts 475 contains treats every git rejection as "not an ancestor", so a git error (missing/pruned object, shallow clone, exec abort) silently drops a legitimate PR with no log, no handleError, and no backoff.
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 399 claimed runs before the lastHash dedup, so a merged/closed PR costs up to two uncached merge-base --is-ancestor spawns on every active tick; only the gh result is cached.
Files Reviewed (1 file in this commit, 2 re-verified)
  • packages/kilo-vscode/tests/unit/am-pr-utils.test.ts - no new issues (the windowsHide: true suggestion is applied)
  • packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts - 2 issues (carried, re-verified)
  • packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts - 1 issue (carried, re-verified)

Notes: fork hygiene is fine (all changes are in packages/kilo-vscode/). No memory leak or unhandled rejection introduced.

Previous Review Summaries (3 snapshots, latest commit 76194f1)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 76194f1)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 4

Fix these issues in Kilo Cloud

Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 350 A guard rejection is forwarded as plain pr: null, so retainPRStatus keeps an already-displayed wrong PR (and its failing actions) until reload; empty()'s :none hash prevents any later retry.
packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts 475 contains treats every git rejection as "not an ancestor", so a git error (missing/pruned object, shallow clone, exec abort) silently drops a legitimate PR with no log, no handleError, and no backoff.
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 399 claimed runs before the lastHash dedup, so a merged/closed PR costs up to two uncached merge-base --is-ancestor spawns on every active tick; only the gh result is cached.
packages/kilo-vscode/tests/unit/am-pr-utils.test.ts 404 The new real-git fixture spawns git without windowsHide: true, unlike the sibling base-update.test.ts fixture, so it can flash a console window on Windows.
Files Reviewed (1 file in this commit, 3 re-verified)
  • packages/kilo-vscode/tests/unit/am-pr-utils.test.ts - 1 issue (new)
  • packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts - 2 issues (carried, re-verified on changed lines)
  • packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts - 1 issue (carried, re-verified on changed lines)

Previous review (commit a6edb7d)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 4

Fix these issues in Kilo Cloud

The follow-up commit a6edb7d removes the redundant git rev-parse HEAD read from related and relies on merge-base --is-ancestor being reflexive, which keeps every guard outcome (PR head == HEAD, head reachable with the merge not, squash-merged, recreated branch name) correct while dropping one git spawn per non-open PR. No behavior regression found. All remaining items are robustness, cost, and test coverage, not correctness.

Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 350 A guard rejection is forwarded as plain pr: null, so retainPRStatus keeps an already-displayed wrong PR (and its failing actions) until reload; empty()'s :none hash prevents any later retry.
packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts 475 contains treats every git rejection as "not an ancestor", so a git error (missing/pruned object, shallow clone, exec abort) silently drops a legitimate PR with no log, no handleError, and no backoff.
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 399 related still runs before the lastHash dedup, so a merged/closed PR costs up to two uncached git spawns per poll; the redundant rev-parse HEAD is gone but the verdict is still recomputed each tick.
packages/kilo-vscode/tests/unit/am-pr-utils.test.ts 367 The "head is the local HEAD" case is now identical to the next test, and the --is-ancestor reflexivity the commit now depends on is asserted only by the stub.
Files Reviewed (2 in this commit, 2 re-verified)
  • packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts - 1 issue (carried, line unchanged; the removal itself is correct)
  • packages/kilo-vscode/tests/unit/am-pr-utils.test.ts - 1 issue (new)
  • packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts - re-verified for the carried findings; not part of this commit
  • packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts - verification context only

Notes: fork hygiene is fine (all changes are in packages/kilo-vscode/). No memory leak or unhandled rejection is introduced: both related git rejections are consumed, and no new timers, listeners, or unbounded maps are added.

Previous review (commit c1195f0)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3

Fix these issues in Kilo Cloud

The core guard is correct and well tested: open/draft PRs short-circuit with no git work, squash-merged and recreated-branch cases are dropped, and legitimate PR-head checkouts (including refs/pull/N/head) are kept. Remaining items are robustness and cost, not correctness on the happy path.

Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 350 A guard rejection is forwarded as plain pr: null, so retainPRStatus keeps an already-displayed wrong PR (and its failing actions) until reload; empty()'s :none hash prevents any later retry.
packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts 471 contains treats every git rejection as "not an ancestor", so a git error (missing/pruned object, shallow clone, exec abort) silently drops a legitimate PR with no log, no handleError, and no backoff.
packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts 399 related is recomputed before the lastHash dedup: up to three git spawns per poll per merged/closed PR, and it repeats the git rev-parse HEAD the seed already ran per worktree.
Files Reviewed (7 files)
  • .changeset/reused-branch-merged-pr.md - no issues; format and user-facing wording match the changeset conventions
  • packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts - 2 issues
  • packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts - 1 issue
  • packages/kilo-vscode/src/agent-manager/pr/am-pr-batch.ts - no issues; mergeCommit { oid } is valid GraphQL and matches the gh pr view --json shape, so the shared parser and hash stay consistent
  • packages/kilo-vscode/src/agent-manager/pr/am-pr-types.ts - no issues
  • packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts - no issues
  • packages/kilo-vscode/tests/unit/am-pr-utils.test.ts - no issues (the git stub cannot distinguish exit 1 from exit 128, so the error path stays uncovered)

Notes: fork hygiene is fine (all changes are in packages/kilo-vscode/). No memory leak or unhandled rejection is introduced: related consumes both git rejections, and no new timers, listeners, or unbounded maps are added. Verified gh pr view --json mergeCommit returns {"oid": ...} for both the legacy and batched paths.


Reviewed by deepseek-v4.1-flash · Input: 59.1K · Output: 9K · Cached: 273.8K

Review guidance: REVIEW.md from base branch main

…hip guard

`merge-base --is-ancestor` is reflexive, so the guard no longer reads HEAD
to compare it with the PR head. A commit git cannot resolve is absent from
the local object store, so it cannot be in HEAD's history either.
Comment thread packages/kilo-vscode/tests/unit/am-pr-utils.test.ts Outdated
Removing `git rev-parse HEAD` made "the PR head is HEAD" a case of "the PR
head is an ancestor", so the two stub tests asserted the same path. Keep one
and assert the exact git calls. The reflexivity the guard now depends on
cannot be modelled by the stub, so check it against a real repository
together with the ancestor and merge-commit cases.
Comment thread packages/kilo-vscode/tests/unit/am-pr-utils.test.ts Outdated
`execFileSync` without `windowsHide` flashes a cmd.exe window on Windows, as
the sibling git fixture in base-update.test.ts already avoids.
@marius-kilocode
marius-kilocode merged commit 14dbd48 into main Sep 15, 2026
25 checks passed
@marius-kilocode
marius-kilocode deleted the fix-pr-batching-logic-errors branch September 15, 2026 07:32
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.

2 participants