Skip to content

Commit 2a513bd

Browse files
iHiDclaude
andauthored
Handle Octokit::Forbidden in GitHub solution syncer (#8470, #8472) (#8479)
When a GitHub App installation loses write permissions to a user's repository, the API returns 403 Forbidden. This was unhandled, causing exceptions to bubble up to Sentry. Now treated as a noop since the permission issue is permanent and retrying won't help. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7c04247 commit 2a513bd

10 files changed

Lines changed: 78 additions & 2 deletions

File tree

app/commands/user/github_solution_syncer/create_pull_request.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ def call
2323
pr_title,
2424
pr_message
2525
)
26-
rescue Octokit::NotFound
27-
# Repo may have been deleted or renamed — nothing to sync to
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
2829
nil
2930
end
3031

app/commands/user/github_solution_syncer/sync_everything.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def call
1717
end
1818
rescue GithubApp::InstallationNotFoundError
1919
# noop - installation may have been removed or GitHub may be having issues
20+
rescue Octokit::Forbidden
21+
# noop - the integration no longer has permission to access the repo
2022
rescue Octokit::ServerError
2123
requeue_job!(30.seconds)
2224
end

app/commands/user/github_solution_syncer/sync_iteration.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def call
2020
end
2121
rescue GithubApp::InstallationNotFoundError
2222
# noop - installation may have been removed or GitHub may be having issues
23+
rescue Octokit::Forbidden
24+
# noop - the integration no longer has permission to access the repo
2325
rescue Octokit::ServerError
2426
requeue_job!(30.seconds)
2527
end

app/commands/user/github_solution_syncer/sync_solution.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def call
1717
end
1818
rescue GithubApp::InstallationNotFoundError
1919
# noop - installation may have been removed or GitHub may be having issues
20+
rescue Octokit::Forbidden
21+
# noop - the integration no longer has permission to access the repo
2022
rescue Octokit::ServerError
2123
requeue_job!(30.seconds)
2224
end

app/commands/user/github_solution_syncer/sync_track.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def call
1717
end
1818
rescue GithubApp::InstallationNotFoundError
1919
# noop - installation may have been removed or GitHub may be having issues
20+
rescue Octokit::Forbidden
21+
# noop - the integration no longer has permission to access the repo
2022
rescue Octokit::ServerError
2123
requeue_job!(30.seconds)
2224
end

test/commands/user/github_solution_syncer/create_pull_request_test.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,22 @@ class CreatePullRequestTest < ActiveSupport::TestCase
5353
result = User::GithubSolutionSyncer::CreatePullRequest.(syncer, "title", "body", &block)
5454
assert_nil result
5555
end
56+
57+
test "returns nil when integration lacks permission" do
58+
syncer = create :user_github_solution_syncer
59+
60+
token = "fake-token-#{SecureRandom.uuid}"
61+
GithubApp.expects(:generate_installation_token!).with(syncer.installation_id).returns(token)
62+
63+
client = mock
64+
Octokit::Client.expects(:new).with(access_token: token).returns(client).at_least_once
65+
client.expects(:repository).with(syncer.repo_full_name).raises(Octokit::Forbidden)
66+
67+
block = ->(_, _) {}
68+
block.expects(:call).never
69+
70+
result = User::GithubSolutionSyncer::CreatePullRequest.(syncer, "title", "body", &block)
71+
assert_nil result
72+
end
5673
end
5774
end

test/commands/user/github_solution_syncer/sync_everything_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ class SyncEverythingTest < ActiveSupport::TestCase
1212
User::GithubSolutionSyncer::SyncEverything.(user)
1313
end
1414

15+
test "noops when integration lacks permission" do
16+
user = create(:user)
17+
create(:user_github_solution_syncer, user:)
18+
19+
CreatePullRequest.stubs(:call).raises(Octokit::Forbidden)
20+
21+
# Should not raise
22+
User::GithubSolutionSyncer::SyncEverything.(user)
23+
end
24+
1525
test "requeues on server error" do
1626
user = create(:user)
1727
create(:user_github_solution_syncer, user:)

test/commands/user/github_solution_syncer/sync_iteration_test.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ class SyncIterationTest < ActiveSupport::TestCase
1717
User::GithubSolutionSyncer::SyncIteration.(iteration)
1818
end
1919

20+
test "noops when integration lacks permission" do
21+
user = create(:user)
22+
track = create(:track, slug: "ruby")
23+
exercise = create(:practice_exercise, track:, slug: "two-fer")
24+
solution = create(:practice_solution, user:, exercise:)
25+
submission = create(:submission, solution:)
26+
iteration = create(:iteration, user:, solution:, submission:)
27+
create(:user_github_solution_syncer, user:)
28+
29+
CreatePullRequest.stubs(:call).raises(Octokit::Forbidden)
30+
31+
# Should not raise
32+
User::GithubSolutionSyncer::SyncIteration.(iteration)
33+
end
34+
2035
test "requeues on server error" do
2136
user = create(:user)
2237
track = create(:track, slug: "ruby")

test/commands/user/github_solution_syncer/sync_solution_test.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ class SyncSolutionTest < ActiveSupport::TestCase
1515
User::GithubSolutionSyncer::SyncSolution.(solution)
1616
end
1717

18+
test "noops when integration lacks permission" do
19+
user = create(:user)
20+
track = create(:track, slug: "ruby")
21+
exercise = create(:practice_exercise, track:, slug: "two-fer")
22+
solution = create(:practice_solution, user:, exercise:)
23+
create(:user_github_solution_syncer, user:)
24+
25+
CreatePullRequest.stubs(:call).raises(Octokit::Forbidden)
26+
27+
# Should not raise
28+
User::GithubSolutionSyncer::SyncSolution.(solution)
29+
end
30+
1831
test "requeues on server error" do
1932
user = create(:user)
2033
track = create(:track, slug: "ruby")

test/commands/user/github_solution_syncer/sync_track_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ class SyncTrackTest < ActiveSupport::TestCase
1414
User::GithubSolutionSyncer::SyncTrack.(user_track)
1515
end
1616

17+
test "noops when integration lacks permission" do
18+
user = create(:user)
19+
track = create(:track, slug: "ruby")
20+
user_track = create(:user_track, user:, track:)
21+
create(:user_github_solution_syncer, user:)
22+
23+
CreatePullRequest.stubs(:call).raises(Octokit::Forbidden)
24+
25+
# Should not raise
26+
User::GithubSolutionSyncer::SyncTrack.(user_track)
27+
end
28+
1729
test "requeues on server error" do
1830
user = create(:user)
1931
track = create(:track, slug: "ruby")

0 commit comments

Comments
 (0)