Skip to content

Commit e093020

Browse files
iHiDclaude
andauthored
Handle Octokit::UnprocessableEntity in GitHub solution syncer PR creation (#8609)
When there are no commits between the base and sync branches, GitHub returns a 422 error. This can happen when the sync process determines there are changes but the resulting commits don't differ from the base. Handle this the same way as NotFound and Forbidden — as a permanent failure that doesn't need retrying. Closes #8596 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ea75b25 commit e093020

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

app/commands/user/github_solution_syncer/create_pull_request.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ def call
2323
pr_title,
2424
pr_message
2525
)
26-
rescue Octokit::NotFound, Octokit::Forbidden
27-
# Repo may have been deleted/renamed, or the integration
28-
# no longer has permission — nothing to sync to
26+
rescue Octokit::NotFound, Octokit::Forbidden, Octokit::UnprocessableEntity
27+
# Repo may have been deleted/renamed, the integration no longer
28+
# has permission, or there are no commits between branches
2929
nil
3030
end
3131

test/commands/user/github_solution_syncer/create_pull_request_test.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,33 @@ class CreatePullRequestTest < ActiveSupport::TestCase
7070
result = User::GithubSolutionSyncer::CreatePullRequest.(syncer, "title", "body", &block)
7171
assert_nil result
7272
end
73+
74+
test "returns nil when PR validation fails" do
75+
syncer = create :user_github_solution_syncer
76+
77+
hex_8 = SecureRandom.hex(8)
78+
SecureRandom.expects(:hex).with(8).returns(hex_8)
79+
80+
token = "fake-token-#{SecureRandom.uuid}"
81+
GithubApp.expects(:generate_installation_token!).with(syncer.installation_id).returns(token)
82+
83+
client = mock
84+
Octokit::Client.expects(:new).with(access_token: token).returns(client).at_least_once
85+
86+
base_branch = "main"
87+
base_sha = "base-commit-sha"
88+
new_branch = "exercism-sync/#{hex_8}"
89+
client.expects(:repository).with(syncer.repo_full_name).returns(mock(default_branch: base_branch))
90+
client.expects(:branch).with(syncer.repo_full_name, base_branch).returns(mock(commit: mock(sha: base_sha)))
91+
client.expects(:create_ref).with(syncer.repo_full_name, "heads/#{new_branch}", base_sha)
92+
93+
block = ->(_, _) {}
94+
block.expects(:call).with(new_branch, token).returns(true)
95+
96+
client.expects(:create_pull_request).raises(Octokit::UnprocessableEntity)
97+
98+
result = User::GithubSolutionSyncer::CreatePullRequest.(syncer, "title", "body", &block)
99+
assert_nil result
100+
end
73101
end
74102
end

0 commit comments

Comments
 (0)