tagpr で 'The search is longer than 256 characters' とエラー出たが GitHub の Search API の Validation が謎だった
tagpr を利用しているリポジトリの workflow で以下のようなエラーが発生した
(Org名/リポジトリ名は伏せている)
GET https://api.github.com/search/issues?q=repo%3Afoo%2Fbar+is%3Apr+is%3Aclosed+a3dbce5+efe8b21+33b2841+57329cf+ae236ff+c154ae2+ce4d1f9+571a330+6296351+50d269e+597c876+5b591fb+acc2876+ecb195d+b6032a1+3ba8c8d+fdce965+c1e99b3+dbccbab+22911dc+13da159+16e3d88+08eb384+bccdaa6+b1172ba+dbb2169: 422 Validation Failed [{Resource:Search Field:q Code:invalid Message:The search is longer than 256 characters.}]
クエリ部分を読みやすくデコードすると以下のようになる
repo:foo/bar is:pr is:closed a3dbce5 efe8b21 33b2841 57329cf ae236ff c154ae2 ce4d1f9 571a330 6296351 50d269e 597c876 5b591fb acc2876 ecb195d b6032a1 3ba8c8d fdce965 c1e99b3 dbccbab 22911dc 13da159 16e3d88 08eb384 bccdaa6 b1172ba dbb2169
同じ現象に遭遇したのは自分だけでは無さそう
tagpr を調べる
該当しそうな場所
GitHub API の制限を調べる
query の長さ制限について
Limitations on query length
You cannot use queries that:
- are longer than 256 characters (not including operators or qualifiers).
- have more than five AND, OR, or NOT operators.
These search queries will return a "Validation failed" error message.
query の構造
A query can contain any combination of search qualifiers supported on GitHub. The format of the search query is:
SEARCH_KEYWORD_1 SEARCH_KEYWORD_N QUALIFIER_1 QUALIFIER_N
エラーになった query で以下の部分は qualifier
repo:foo/bar is:pr is:closed
以下の部分が search keyword
a3dbce5 efe8b21 33b2841 ... (snip)
再現してみる
#!/bin/bash
# GitHub API endpoint
url="https://api.github.com/search/issues"
# search query
qualifiers="repo:foo/bar is:pr is:closed"
# keywords="a3dbce5 efe8b21 33b2841 57329cf ae236ff c154ae2 ce4d1f9 571a330 6296351 50d269e 597c876 5b591fb acc2876 ecb195d b6032a1 3ba8c8d fdce965 c1e99b3 dbccbab 22911dc 13da159 16e3d88 08eb384 bccdaa6 b1172ba dbb2169" # NG
keywords="e9c8744 9730145 cbf227f e9a9a1e 7749956 593178a 7df9cae 01ae53d a607855 bd7d0c5 4610ac3 baf17b3 2e61f3c ed3a4be 75a6513 2559d2a 52dea6f 2887d77 c31f660 95f0247 3b35393 63eafc4 3144034 b7a8414 ad35d6f 298e57a" # OK
echo "keywords (${#keywords}): $keywords"
echo "---------------------------------"
query="$qualifiers $keywords"
# URL encode
encoded_query=$(echo "$query" | jq -sRr @uri)
# run curl command
curl -i -H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $GH_TOKEN" \
"$url?q=$encoded_query"
keywords
の部分が同じ長さに見えるけど、エラーになるケースとならないケースある
エラーにならない
keywords (207): e9c8744 9730145 cbf227f e9a9a1e 7749956 593178a 7df9cae 01ae53d a607855 bd7d0c5 4610ac3 baf17b3 2e61f3c ed3a4be 75a6513 2559d2a 52dea6f 2887d77 c31f660 95f0247 3b35393 63eafc4 3144034 b7a8414 ad35d6f 298e57a
結果
{
"total_count": 0,
"incomplete_results": false,
"items": [
]
}
エラーになる
keywords (207): a3dbce5 efe8b21 33b2841 57329cf ae236ff c154ae2 ce4d1f9 571a330 6296351 50d269e 597c876 5b591fb acc2876 ecb195d b6032a1 3ba8c8d fdce965 c1e99b3 dbccbab 22911dc 13da159 16e3d88 08eb384 bccdaa6 b1172ba dbb2169
結果
{
"message": "Validation Failed",
"errors": [
{
"message": "The search is longer than 256 characters.",
"resource": "Search",
"field": "q",
"code": "invalid"
}
],
"documentation_url": "https://docs.github.com/v3/search/",
"status": "422"
}
これは検証方法がミスっている
前述のスクリプトで発行しているクエリのスペースの区切り方が本来のものと違っていること気付いた...
スクリプトを修正
#! /bin/bash
# To run this script, the environment variable GH_TOKEN must be set for tokens with access to repositories such as PAT.
qualifiers="repo%3Asnaka%2Ftagpr-err-reproduce+is%3Apr+is%3Aclosed"
keywords="a3dbce5+efe8b21+33b2841+57329cf+ae236ff+c154ae2+ce4d1f9+571a330+6296351+50d269e+597c876+5b591fb+acc2876+ecb195d+b6032a1+3ba8c8d+fdce965+c1e99b3+dbccbab+22911dc+13da159+16e3d88+08eb384+bccdaa6+b1172ba+dbb2169" # 1. NG
query="${qualifiers}+${keywords}"
# < Replacing the following keyword with the line above confirms the instability of the results. >
#
# keywords="a3dbce5+efe8b21+33b2841+57329cf+ae236ff+c154ae2+ce4d1f9+571a330+6296351+50d269e+597c876+5b591fb+acc2876+ecb195d+b6032a1+3ba8c8d+fdce965+c1e99b3+dbccbab+22911dc+13da159+16e3d88+08eb384+bccdaa6+b1172ba+dbb2169" # 1. NG
# keywords="a3dbce5+efe8b21+33b2841+57329cf+ae236ff+c154ae2+ce4d1f9+571a330+6296351+50d269e+597c876+5b591fb+acc2876+ecb195d+b6032a1+3ba8c8d+fdce965+c1e99b3+dbccbab+22911dc+13da159+16e3d88+08eb384+bccdaa6+b1172ba+dbb216" # 2. OK
# keywords="e9c8744+9730145+cbf227f+e9a9a1e+7749956+593178a+7df9cae+01ae53d+a607855+bd7d0c5+4610ac3+baf17b3+2e61f3c+ed3a4be+75a6513+2559d2a+52dea6f+2887d77+c31f660+95f0247+3b35393+63eafc4+3144034+b7a8414+ad35d6f+298e57a" # 3. OK
# keywords="e9c8744+9730145+cbf227f+e9a9a1e+7749956+593178a+7df9cae+01ae53d+a607855+bd7d0c5+4610ac3+baf17b3+2e61f3c+ed3a4be+75a6513+2559d2a+52dea6f+2887d77+c31f660+95f0247+3b35393+63eafc4+3144034+b7a8414+ad35d6f+298e57abcd" # 4. OK
# keywords="0000000+000000a+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa" # 5. NG
# keywords="0000000+000000a+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaaa+aaaaaa" # 6. OK
# The API specification states 'not including operators or qualifiers', so the number of
# characters in the non-qualifiers part (keywords) is counted.
echo "keywords char length: ${#keywords}"
echo "query char length: ${#query}"
echo "---------------------------------"
curl -H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $GH_TOKEN" \
"https://api.github.com/search/issues?q=${query}"
とりあえず回避してみる
問題の発生状況
- 前回のタグから最新まで commit を取得している
- 初回リリースに含まれる commit 数が多い
- 検索する commit 件数が検索APIの query の最大長に収まっている(はず)にもかかわらず、 API 側から「256 文字を超えている」という判定結果が返ってくる
対応
- リリースに含まれる commit 件数を削減する
quailfier として与えるリポジトリ名が文字数制限に影響するかどうか確認する
仕様によると qualifier は文字数の制限に含まれないと理解しているが、本当にそうなのか?
clone した方のリポジトリの workflow でエラーが出てた
POST https://api.github.com/repos/snaka/tagpr-err-reproduce-with-veeeeeeeeeeeeeeeeeeeeeeeeeeeeery-long-repository-namery-long/releases/generate-notes: 403 Resource not accessible by integration []
毎回これ忘れてる
YAML の設定だけではダメでこの設定は必要そう?
Settings > Actions > General
上記リポジトリ設定で動いた
API のバリデーション結果を見る限り、qualifier に含まれているリポジトリ名の長さには関係が無さそう。
つまり、検索ワードのバリデーションには qualifier は含まれていない ( そう書いていた )
エラーを再現させてみる
tagpr のロジックでは qualifier も含めて文字数を計算している。
その計算の前提が間違っているとしたら、リポジトリ名が極端に短い場合は確実にエラーを再現できるはず。
その仮定を検証するためのリポジトリ
再現できた
GET https://api.github.com/search/issues?q=repo%3Asnaka%2Fa+is%3Apr+is%3Aclosed+0aa9d3e+10e7dfc+b8c0d32+a60b1f9+67c2d72+8750b6e+9a6fa3d+ed6313d+e9c8744+9730145+cbf227f+e9a9a1e+7749956+593178a+7df9cae+01ae53d+a607855+bd7d0c5+4610ac3+baf17b3+2e61f3c+ed3a4be+75a6513+2559d2a+52dea6f+2887d77+c31f660+95f0247: 422 Validation Failed [{Resource:Search Field:q Code:invalid Message:The search is longer than 256 characters.}]
Go でURIエスケープの方法を確認する
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
"net/url"
)
func main() {
qualifier := "repo:snaka/a is:pr is:closed"
keywords := "10e7dfc b8c0d32 a60b1f9 67c2d72 8750b6e 9a6fa3d"
query := qualifier + " " + keywords
fmt.Println("query:", query)
fmt.Println("escaped(query):", url.QueryEscape(query))
fmt.Println("escaped(path) :", url.PathEscape(query))
}
実行結果
query: repo:snaka/a is:pr is:closed 10e7dfc b8c0d32 a60b1f9 67c2d72 8750b6e 9a6fa3d
escaped(query): repo%3Asnaka%2Fa+is%3Apr+is%3Aclosed+10e7dfc+b8c0d32+a60b1f9+67c2d72+8750b6e+9a6fa3d
escaped(path) : repo:snaka%2Fa%20is:pr%20is:closed%2010e7dfc%20b8c0d32%20a60b1f9%2067c2d72%208750b6e%209a6fa3d
上の結果から、
スペースを%20
に変換するのには url.PathEscape
のほうが使えそうだが、 query 文字列のエスケープに PathEscape
を使う気持ち悪さもある
得られる結果がたまたま同じという理由で、違う用途のものを使うのに抵抗ある。
おもそも、APIにおける文字数カウント方法についてのロジックが公開されていない状態で、 PathEscape
がそのAPI側の仕様にマッチしているという保証も無く、逆に困る場面も無いとは言えない。
普通に %20
で join するだけでも良さそう
普通に %20 で join するだけでも良さそう
真偽不明な仕様を持ち込むより、上限に余裕をもたせる方針に着地した。