Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion internal/reviewer/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2121,7 +2121,9 @@ func (r *Runner) runReviewStep(ctx context.Context, input stepInput) (reviewerCh
}
message := reviewerAgentFailureMessage("review", result, fmt.Sprintf("Reviewer agent %s", result.Status))
kind := FailureRetryableTransient
if agent.IsAgentSetupFailureMessage(message) {
if isGitHubSelfApprovalFailure(message) {
kind = FailureNonRetryable
} else if agent.IsAgentSetupFailureMessage(message) {
kind = FailureManualIntervention
}
return checkpoint, &loopError{message: message, kind: kind}
Expand Down Expand Up @@ -2462,6 +2464,12 @@ func reviewerAgentFailureMessage(phase string, result AgentResult, fallback stri
return strings.Join(parts, "; ")
}

func isGitHubSelfApprovalFailure(message string) bool {
normalized := strings.ToLower(message)
return strings.Contains(normalized, "can not approve your own pull request") ||
strings.Contains(normalized, "cannot approve your own pull request")
}

func summarizeAgentStderr(stderr string) string {
stderr = strings.TrimSpace(stderr)
if len(stderr) <= 240 {
Expand Down
31 changes: 31 additions & 0 deletions internal/reviewer/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4053,6 +4053,37 @@ func TestProcessClaimedItemRetryAfterReviewFailureRepreparesWorktree(t *testing.
}
}

func TestProcessClaimedItemDoesNotRetryGitHubSelfApprovalFailure(t *testing.T) {
t.Parallel()
fixture := newRunnerFixture(t)
github := &fakeGitHubGateway{reviewMarkerMissing: true}
agent := &fakeAgentExecutor{results: []AgentResult{{Status: "failed", Summary: `submit validated PR review: HTTP 422: Review Can not approve your own pull request`}}}
runner := New(Options{DB: fixture.coordinator.DB(), Repos: fixture.repos, GitHub: github, Git: &fakeGitGateway{}, AgentExecutor: agent, Logger: fixture.logger, Now: fixture.now})

if _, err := runner.DiscoverPullRequests(context.Background(), DiscoveryInput{ProjectID: "project_1", Repo: "acme/looper"}); err != nil {
t.Fatalf("DiscoverPullRequests() error = %v", err)
}
claim, err := fixture.repos.Queue.ClaimNextOfType(context.Background(), fixture.nowISO(), "reviewer-worker-1", "reviewer")
if err != nil || claim == nil {
t.Fatalf("ClaimNextOfType() = (%#v, %v), want claimed item", claim, err)
}

result, err := runner.ProcessClaimedItem(context.Background(), *claim)
if err != nil {
t.Fatalf("ProcessClaimedItem() error = %v", err)
}
if result.Status != "failed" || result.FailureKind != FailureNonRetryable {
t.Fatalf("result = %#v, want non_retryable self-approval failure", result)
}
queue, err := fixture.repos.Queue.GetByID(context.Background(), claim.ID)
if err != nil || queue == nil {
t.Fatalf("Queue.GetByID() = (%#v, %v), want queue", queue, err)
}
if queue.Status != "failed" || queue.LastErrorKind == nil || *queue.LastErrorKind != string(FailureNonRetryable) {
t.Fatalf("queue = %#v, want failed non_retryable queue item", queue)
}
}

func TestProcessClaimedItemRetriesTransientSnapshotFailureInRun(t *testing.T) {
fixture := newRunnerFixture(t)
github := &fakeGitHubGateway{captureSnapshotErrs: []error{&shell.CommandExecutionError{Message: "Command exited with code 1", Result: shell.Result{Stderr: "HTTP 504: We couldn't respond to your request in time"}}}}
Expand Down
Loading