Problem
Currently the agent has two gaps in how it handles PRs:
1. Agent creates duplicate PRs for issues that already have one
ProcessNewIssues only checks for existing PRs by matching the agent's branch name pattern (ai/issue-N) via ListPRsByHead. If a human or another bot has already opened a PR that closes the issue, the agent creates a second, redundant PR.
Current code (loop.go:86-100):
branchName := fmt.Sprintf("ai/issue-%d", issue.Number)
prs, err := a.gh.ListPRsByHead(ctx, a.cfg.Owner, a.cfg.Repo, a.cfg.GitHubHeadOwner, branchName)
if err == nil && len(prs) > 0 {
// only matches ai/issue-N branches
}
2. Agent processes review comments/CI/conflicts on PRs it doesn't own
If a non-agent PR somehow gets tracked in state (e.g., through a future code path or state rebuild), ProcessReviewComments, ProcessCIFailures, and ProcessConflicts would try to address feedback, fix CI, and resolve conflicts on a PR the agent didn't create. This wastes resources and could interfere with human work.
Proposed Solution
Skip issues that already have a closing PR
Before creating a worktree and invoking Claude for an issue, check whether any open PR already closes it. GitHub's Timeline API (GET /repos/{owner}/{repo}/issues/{issue_number}/timeline) includes cross-referenced events with source.issue.pull_request that link PRs to issues. Alternatively, use the GraphQL API's closingReferencesConnection on the Issue type.
Add a new method to GitHubClient:
ListClosingPRs(ctx context.Context, owner, repo string, issueNumber int) ([]PR, error)
In ProcessNewIssues, after confirming the issue is not in state and before creating a worktree:
- Call
ListClosingPRs to find any open PR that would close the issue
- If one exists, log it and skip the issue (do not add to state, do not invoke Claude)
Track PR ownership and skip non-agent PRs
Add an OwnedByAgent bool field to IssueWork to track whether the agent created the PR. Set it to true when the agent creates the PR via Claude, and false when recovering state from a PR the agent didn't create.
In ProcessReviewComments, ProcessCIFailures, and ProcessConflicts:
- Skip work items where
OwnedByAgent == false
In BuildStateFromGitHub (state.go):
- When recovering PRs, check if the PR's head branch matches the
ai/issue-N pattern and the PR author matches cfg.GitHubUser to determine ownership
Files to change
| File |
Change |
pkg/agent/types.go |
Add OwnedByAgent bool to IssueWork |
pkg/agent/github.go |
Add ListClosingPRs to GitHubClient interface + implementation |
pkg/agent/loop.go |
Call ListClosingPRs in ProcessNewIssues before creating worktree; set OwnedByAgent = true when agent creates PR; skip non-owned PRs in ProcessReviewComments, ProcessCIFailures, ProcessConflicts |
pkg/agent/state.go |
Set OwnedByAgent correctly in BuildStateFromGitHub |
pkg/agent/loop_test.go |
Add tests for skipping issues with existing closing PRs and skipping non-owned PRs |
Problem
Currently the agent has two gaps in how it handles PRs:
1. Agent creates duplicate PRs for issues that already have one
ProcessNewIssuesonly checks for existing PRs by matching the agent's branch name pattern (ai/issue-N) viaListPRsByHead. If a human or another bot has already opened a PR that closes the issue, the agent creates a second, redundant PR.Current code (
loop.go:86-100):2. Agent processes review comments/CI/conflicts on PRs it doesn't own
If a non-agent PR somehow gets tracked in state (e.g., through a future code path or state rebuild),
ProcessReviewComments,ProcessCIFailures, andProcessConflictswould try to address feedback, fix CI, and resolve conflicts on a PR the agent didn't create. This wastes resources and could interfere with human work.Proposed Solution
Skip issues that already have a closing PR
Before creating a worktree and invoking Claude for an issue, check whether any open PR already closes it. GitHub's Timeline API (
GET /repos/{owner}/{repo}/issues/{issue_number}/timeline) includescross-referencedevents withsource.issue.pull_requestthat link PRs to issues. Alternatively, use the GraphQL API'sclosingReferencesConnectionon the Issue type.Add a new method to
GitHubClient:In
ProcessNewIssues, after confirming the issue is not in state and before creating a worktree:ListClosingPRsto find any open PR that would close the issueTrack PR ownership and skip non-agent PRs
Add an
OwnedByAgent boolfield toIssueWorkto track whether the agent created the PR. Set it totruewhen the agent creates the PR via Claude, andfalsewhen recovering state from a PR the agent didn't create.In
ProcessReviewComments,ProcessCIFailures, andProcessConflicts:OwnedByAgent == falseIn
BuildStateFromGitHub(state.go):ai/issue-Npattern and the PR author matchescfg.GitHubUserto determine ownershipFiles to change
pkg/agent/types.goOwnedByAgent booltoIssueWorkpkg/agent/github.goListClosingPRstoGitHubClientinterface + implementationpkg/agent/loop.goListClosingPRsinProcessNewIssuesbefore creating worktree; setOwnedByAgent = truewhen agent creates PR; skip non-owned PRs inProcessReviewComments,ProcessCIFailures,ProcessConflictspkg/agent/state.goOwnedByAgentcorrectly inBuildStateFromGitHubpkg/agent/loop_test.go