tagpr で比較的高い頻度で 'Secondary rate limit' となる問題を解決したい
問題
この Issue と同様の現象が割と頻繁に発生する(その日の初回実行時でも発生する)
エラーメッセージ
403 You have exceeded a secondary rate limit. Please wait a few minutes before you try again.
直前になにも実行していないにも関わらず Rate Limit と言われることが多々ある
ワークアラウンド
Private な GitHub App を用意して、その App の Token を利用することで動作が安定したように見える
「create-github-app-token で Token を生成してみる」へ
一旦これで様子を見る
調査のまとめ
わかったこと
- Issue の Search を API で実行したときに該当エラーが発生している
-
GITHUB_TOKENの生成元によって挙動が変わっていそう- 問題が出ているケース
-
secrets.GITHUB_TOKENを使用している
-
- 問題が出ないケース
- private な GitHub App を用意し、
actions/create-github-app-tokenを使って Token を生成している
- private な GitHub App を用意し、
- 問題が出ているケース
-
git-pr-releaseでも同様の状況は発生していた
わかっていないこと
- 他の類似ツールでは問題が出ていない?
- 類似ツール
git-pr-release
- 類似ツール
-
secrets.GITHUB_TOKENの制限なのか? - GitHub App や Personal Access Token を使う以外の解決方法はないのか?
修正がマージされた
以下で修正されたので close
ローカルで試す
debug 用にログを追加し response のヘッダ情報を出力してみた
検証用リポジトリ をローカルにcloneして tagpr を実行してみる
:
::debug [Search.Issues] x-ratelimit-reset=1726714496
::debug [Search.Issues] x-ratelimit-resource=search
::debug [Search.Issues] x-github-request-id=E7A1:1BDD16:DAE272:E75AD0:66EB925B
::debug [Search.Issues] x-ratelimit-limit=30
::debug [Search.Issues] x-ratelimit-remaining=26
::debug [Search.Issues] x-ratelimit-used=4
::debug [Search.Issues] x-ratelimit-reset=1726714496
::debug [Search.Issues] x-ratelimit-limit=30
::debug [Search.Issues] x-ratelimit-remaining=25
::debug [Search.Issues] x-ratelimit-resource=search
::debug [Search.Issues] x-github-request-id=E7A1:1BDD16:DAE2BB:E75B20:66EB925F
::debug [Search.Issues] x-ratelimit-used=5
:
::debug [Search.Issues] x-ratelimit-remaining=24
::debug [Search.Issues] x-ratelimit-resource=search
::debug [Search.Issues] x-ratelimit-limit=30
::debug [Search.Issues] x-github-request-id=E7AD:3D430D:DEF9BF:EB7383:66EB9273
::debug [Search.Issues] x-ratelimit-reset=1726714496
::debug [Search.Issues] x-ratelimit-used=6
::debug [Search.Issues] x-github-request-id=E7AD:3D430D:DEF9FC:EB73C2:66EB9275
::debug [Search.Issues] x-ratelimit-remaining=23
::debug [Search.Issues] x-ratelimit-used=7
::debug [Search.Issues] x-ratelimit-reset=1726714496
::debug [Search.Issues] x-ratelimit-limit=30
::debug [Search.Issues] x-ratelimit-resource=search
:
::debug [Search.Issues] x-github-request-id=E7B9:3E9C50:DA4E64:E6C828:66EB9288
::debug [Search.Issues] x-ratelimit-used=1
::debug [Search.Issues] x-ratelimit-remaining=29
::debug [Search.Issues] x-ratelimit-limit=30
::debug [Search.Issues] x-ratelimit-reset=1726714568
::debug [Search.Issues] x-ratelimit-resource=search
::debug [Search.Issues] x-ratelimit-used=2
::debug [Search.Issues] x-ratelimit-reset=1726714568
::debug [Search.Issues] x-github-request-id=E7B9:3E9C50:DA4EE4:E6C899:66EB928C
::debug [Search.Issues] x-ratelimit-remaining=28
::debug [Search.Issues] x-ratelimit-limit=30
::debug [Search.Issues] x-ratelimit-resource=search
連続で実行してみたが、 x-ratelimit-remaining の数値は比較的すぐに回復している
x-ratelimit-remaining=26
x-ratelimit-remaining=25
x-ratelimit-remaining=24
x-ratelimit-remaining=23
x-ratelimit-remaining=29 <-- 回復している
x-ratelimit-remaining=28
debug を仕込む
go install でインストールできるようにコードを修正 ( Songmu -> snaka ) する
修正をブランチに push
go install で参照するために tag を打つ (例えば debug-20240919-1613 )
git tag --no-sign debug-20240919-1613
tag を push する
git push origin debug-20240919-1613
修正された tagpr を利用する側
インストール対象は以下のような意味となっている
- リポジトリ
github.com/snaka/tagpr - 対象コマンド(mainパッケージ)のPath
/cmd/tagpr - バージョン(tag)
@debug-20240919-1613
公式の workflow と自分の workflow を比較する
GITHUB_TOKEN の取得方法が異なる
自分の workflow
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
公式(tagpr) の workflow
- name: Generate token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- name: tagpr
run: |
make install
echo '::echo::on'
tagpr
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
create-github-app-token で Token を生成してみる
やること
- Register new GitHub App
-
Store the App's ID in your repository environment variables (example:
APP_ID) -
Store the App's private key in your repository secrets (example:
PRIVATE_KEY)
GitHub App を登録する



App に権限を付与する


Appへリポジトリのアクセスを許可する


pem がダウンロードされるので控えておく
APP ID と Private Key を secret に設定しておく

workflow の中で create-github-app-token を呼び出し Token を生成する
diff --git a/.github/workflows/run_tagpr.yml b/.github/workflows/run_tagpr.yml
index f1e6ca8..3eb5c61 100644
--- a/.github/workflows/run_tagpr.yml
+++ b/.github/workflows/run_tagpr.yml
@@ -18,6 +18,12 @@ jobs:
with:
go-version: '1.21'
- run: go install github.com/snaka/tagpr/cmd/tagpr@debug-20240919-1708
+ - name: Generate token
+ id: generate_token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ secrets.TAGPR_APP_ID }}
+ private-key: ${{ secrets.TAGPR_PRIVATE_KEY }}
- run: tagpr
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
secrets.GITHUB_TOKEN 使ってたときに比べて、こちらのほうが安定している。
何度か実行してみても Secondary Rate Limit のエラーは出てこない。
類似ツール: git-pr-release を調べる
git-pr-release を使っているProjectの workflow 実行ログ見ると同様のエラーが出ているのを見つけた
workflow log
/opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/octokit-9.1.0/lib/octokit/response/raise_error.rb:14:in `on_complete': GET https://api.github.com/search/issues?q=repo%3Axxxxxxxxxxxxxxx+is%3Apr+is%3Aclosed+a703494: 403 - You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID 4BC0:A2B2B:1E37741:375C2A4:669F762E. // See: https://docs.github.com/free-pro-team@latest/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits (Octokit::TooManyRequests)
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/faraday-2.10.0/lib/faraday/middleware.rb:60:in `block in call'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/faraday-2.10.0/lib/faraday/response.rb:42:in `on_complete'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/faraday-2.10.0/lib/faraday/middleware.rb:59:in `call'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/octokit-9.1.0/lib/octokit/middleware/follow_redirects.rb:73:in `perform_with_redirection'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/octokit-9.1.0/lib/octokit/middleware/follow_redirects.rb:61:in `call'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/faraday-2.10.0/lib/faraday/rack_builder.rb:152:in `build_response'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/faraday-2.10.0/lib/faraday/connection.rb:444:in `run_request'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/faraday-2.10.0/lib/faraday/connection.rb:200:in `get'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/sawyer-0.9.2/lib/sawyer/agent.rb:99:in `call'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/octokit-9.1.0/lib/octokit/connection.rb:156:in `request'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/octokit-9.1.0/lib/octokit/connection.rb:84:in `paginate'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/octokit-9.1.0/lib/octokit/client/search.rb:99:in `search'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/octokit-9.1.0/lib/octokit/client/search.rb:49:in `search_issues'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/git-pr-release-2.2.0/lib/git/pr/release/cli.rb:145:in `search_issue_numbers'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/git-pr-release-2.2.0/lib/git/pr/release/cli.rb:179:in `fetch_squash_merged_pr_numbers_from_github'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/git-pr-release-2.2.0/lib/git/pr/release/cli.rb:101:in `fetch_merged_prs'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/git-pr-release-2.2.0/lib/git/pr/release/cli.rb:46:in `start'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/git-pr-release-2.2.0/lib/git/pr/release/cli.rb:12:in `start'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/git-pr-release-2.2.0/exe/git-pr-release:5:in `<top (required)>'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/bin/git-pr-release:23:in `load'
from /opt/hostedtoolcache/Ruby/3.0.1/x64/bin/git-pr-release:23:in `<main>'
backtrace 見た感じ issue に対する search を実行している箇所で発生しているので、同じ状況と思われる
from /opt/hostedtoolcache/Ruby/3.0.1/x64/lib/ruby/gems/3.0.0/gems/git-pr-release-2.2.0/lib/git/pr/release/cli.rb:145:in `search_issue_numbers'
しかし、workflow 自体は失敗していないので、リトライ機構が用意されているのかもしれない?
git-pr-release の該当箇所
Octokit の search_issues
search の実装は以下
Octokit 自身には Retry 機構は入って無さそうだけど、 faraday-retry が何かやっていそう?
Octokit では特に Retry してなさそう... と思ったら、 workflow 側で 以下のようにやっていて、エラーを無視して進めていた
git-pr-release --squashed || echo 'Done.'
go-github-ratelimit の使い方を調べる
READMEより
rateLimiter, err := github_ratelimit.NewRateLimitWaiterClient(nil)
if err != nil {
panic(err)
}
client := github.NewClient(rateLimiter).WithAuthToken("your personal access token")
RateLimitWaiterClient を github.NewClient() の第一引数(httpClient) として渡す
NewRateLimitWaiterClient(nil)
http.Client{ Transport: waiter } とは?
Gemini に訊いてみると以下のような回答
Transportフィールドは、http.Clientが実際に HTTP リクエストを送信する方法を制御するインターフェースです。waiterはSecondaryRateLimitWaiter型であり、これはおそらく API の二次的なレート制限に対処するためのカスタムのhttp.RoundTripper実装です。http.RoundTripperインターフェースは、HTTP リクエストを処理し、レスポンスを返すためのメソッドRoundTripを定義します。
http.RoundTripper について
現状の tagpr では以下のように oauth2.Client が Transport として渡されるようになっている
前述のようにgithub_ratelimit.NewRateLimitWaiterClient() の第一引数が RoundTripper ( Transport プロパティ ) を受け取るようになっているので、そこに oauthClient を渡すと良さそう?