表計算に並んだ 40 行を、1 行ずつ Claude に渡して整える自動化を組んだ夜でした。ストアの説明文を言語ごとに直す作業を、手で繰り返すのに疲れて始めたものです。実行ボタンを押すと、画面は緑のまま進み、14 行目で赤く止まりました。
同じ形の相談は、ノーコードツールのコミュニティでもよく見かけます。「途中までは動くのに、なぜか止まる」。原因は Claude 側の故障ではなく、1 分あたりに受け付ける件数の上限にぶつかる 429 でした。
最初にお伝えしたいのは、直し方が「待つ」ことに尽きるという点です。ただし、何秒待つかは勘ではなく、割り算で決められます。その式と、失敗した行を黙って落とさない設定を、ここに書き残します。
429 は「壊れた」ではなく「速すぎる」の合図
Claude API は、1 分あたりのリクエスト数と、入力・出力トークン数に上限を持っています。上限を超えると、API は 429 を返します。返答には retry-after というヘッダーが付き、何秒待てば再開できるかの目安が入っています。
もう一つ知っておきたいのは、上限の数え方です。「毎分ゼロ時に回復」ではなく、バケツに水が少しずつ戻るような仕組みで、一定の間隔で少しずつ余裕が戻ります。最初の数件は通り、バケツが空になったあたりで 429 が出始める、という動き方をします。冒頭の「14 行目」は、まさにこの空になる瞬間でした。
自分の上限は、Console の制限の画面で確かめられます。以下では、読者の方が数字を差し替えやすいよう、上限を毎分 10 件に置いた模擬で説明します。
模擬バケットで 7 通りの間隔を比べました
本物の API ではなく、手元で動く模擬のバケットを使っています。上限は毎分 10 件、最初は満タンで 10 件ぶんの余裕があり、1 回の呼び出しに 1.5 秒かかる、という条件です。行は 40 件。時間は仮想で進めるので、実際に待つ必要はありません。
# sim429.py — 仮想時間で動く模擬バケット(本物の API ではありません)
class Bucket:
def __init__(self, rpm, cap):
self.rate = rpm / 60.0 # 1秒あたりに戻る量
self.cap = cap # 最初に持っている余裕
self.tokens = cap
self.t = 0.0
def take(self, now):
# 経過時間ぶん余裕を戻してから、1件ぶん取れるか調べる
self.tokens = min(self.cap, self.tokens + (now - self.t) * self.rate)
self.t = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
def run(n, rpm, cap, latency, spacing=0.0, retry_wait=None,
max_attempts=3, stop_on_error=False):
b = Bucket(rpm, cap)
now = 0.0
calls = r429 = done = 0
for i in range(n):
attempts = 0
while True:
attempts += 1
calls += 1
ok = b.take(now)
now += latency # 呼び出しにかかる時間
if ok:
done += 1
break
r429 += 1
if stop_on_error: # エラーで全体が止まる設定
return dict(done=done, calls=calls, r429=r429,
secs=round(now), stopped_at=i + 1)
if retry_wait is None or attempts >= max_attempts:
break # 再試行しない/回数切れ=この行は空のまま
now += retry_wait
now += spacing # 次の行までの Sleep
return dict(done=done, calls=calls, r429=r429, secs=round(now))
N, RPM, CAP, LAT = 40, 10, 10, 1.5
print("A 間隔なし・最初の429で停止", run(N, RPM, CAP, LAT, stop_on_error=True))
print("B 間隔なし・60秒待って再試行", run(N, RPM, CAP, LAT, retry_wait=60))
print("C 間隔なし・失敗は無視", run(N, RPM, CAP, LAT))
print("D 毎件 6.0 秒 Sleep", run(N, RPM, CAP, LAT, spacing=6.0))
print("E 毎件 4.5 秒 Sleep", run(N, RPM, CAP, LAT, spacing=4.5))走らせた結果を、表にまとめます。
| 設定 | 成功した行 | 429 の回数 | 所要時間 | わかったこと |
|---|---|---|---|---|
| A 間隔なし・エラーで停止 | 13 / 40 | 1 | 21 秒 | 14 行目で止まる。冒頭の症状そのもの |
| B 間隔なし・60 秒待って再試行 | 40 / 40 | 3 | 244 秒 | 最後は全件通るが、60 秒の待ちが 3 回入る |
| C 間隔なし・失敗は無視 | 19 / 40 | 21 | 60 秒 | 画面は緑のまま、21 行が空欄になる |
| D 毎件 6.0 秒 Sleep | 40 / 40 | 0 | 300 秒 | 429 は出ない。ただし少し待ちすぎ |
| E 毎件 4.5 秒 Sleep | 40 / 40 | 0 | 240 秒 | 429 は出ず、D より 60 秒短い |
意外だったのは、C です。いちばん早く終わり、エラーも表に出ません。ところが 21 行が空のままで、気づくのは翌朝、表計算を開いたときです。「止まる」より「止まらずに欠ける」ほうが、はるかに厄介でした。
Sleep の秒数は「60 ÷ 上限 − 呼び出しの時間」
D と E の差が、この記事でいちばんお伝えしたい点です。毎分 10 件なら、1 件あたり 6 秒の間隔があれば足ります。ただし、その 6 秒には、呼び出し自体にかかった 1.5 秒も含まれます。Sleep を 6 秒にすると、実際の間隔は 7.5 秒になってしまうのです。
Sleep の秒数 = 60 ÷ 毎分の上限 − 1回の呼び出しにかかる時間
例: 60 ÷ 10 − 1.5 = 4.5 秒呼び出しの時間は、Make の実行履歴で、Claude のモジュールにかかった秒数を数件ぶん見れば、おおよそ分かります。長い文章を返させる作業ほど長くなるので、ここは実測が確かです。
もう一つ、余裕を 1 割ほど足しておくことを、私自身は好んでいます。別のシナリオが同じキーを使っていると、こちらの計算どおりには戻らないためです。個人開発で多言語のストア文面を扱っていると、複数の作業が同じ API キーを共有する場面が多く、ぴったりの計算は、ほとんど当たりません。
Make 側で触る場所は 3 つ
ここからは Make での置き場所です。画面の表記は更新で変わることがあるので、名前は目安としてご覧ください。なお、この記事の数字は上の模擬で確かめたもので、Make のシナリオそのものは動かしておりません。
| 目的 | 置く場所 | 設定の考え方 |
|---|---|---|
| 件数ごとの間隔 | 繰り返しの中、Claude のモジュールの直前か直後に Sleep | 上の式で出した秒数。上限を超える秒数は入らないので、長いときは件数を分ける |
| 429 のときだけ待つ | Claude モジュールのエラー処理に Break | 再試行の回数と間隔を入れる。間隔は 60 秒より短くてもよい |
| 失敗行を落とさない | Ignore ではなく Break を選ぶ | 中断された実行が保存され、あとからやり直せる状態にしておく |
Ignore を選ぶと C と同じ結果になります。Break なら、失敗した行が「未完了の実行」として残るので、翌朝に空欄を探し回らずに済みます。胃が痛くなる朝を一度経験してから、私はここだけは Break と決めました。
待つ秒数は割り算で決め、失敗は無視せず残します。 この線引きは、行数が増えても崩さないようにしています。いま思えば、最初のうちは「止まらないこと」ばかりを気にして、欠けることには目が向いていなかったのだと気づきました。
つまずいたら、この順で見てください
止まったときは、次の順で確かめると、原因に届きやすくなります。
- 実行履歴で、止まった行の番号と、返ってきたエラーの番号(429 か、別の番号か)を確かめます。
- 429 なら、Console の制限の画面で、毎分の件数とトークン数のどちらが上限に近いかを見ます。
- 件数が原因なら Sleep を、トークンが原因なら 1 回に渡す文章の長さを見直します。
429 の中身や、retry-after を読む実装に踏み込みたい方は、Claude API のリトライを書いたらレート制限が悪化した話 — Retry-After を読まないリトライの罠が、コードを書く側の視点で参考になります。また、本番でヘッダーから余白を測る運用は、Claude API が本番で突然 429 を返し始めたときにまとめました。
まずは、手持ちのシナリオの Claude モジュールの隣に、4〜5 秒の Sleep を 1 つ置くところから始めていただければと思います。私もそこから始めました。