第051話 「毎日正午の再試行を予約した」ら、その予約が自分で自分を消せずに残り続けていた話
いつ困る?
「終わったら自分で片付ける」仕組みって、便利そうに見えますよね。使い捨ての予約が終わったら自分で予約を解除する——余計なものを残さない、一見完璧な設計です。
でもこの「自分で片付ける」、コードの置き場所を間違えると一度も実行されないという落とし穴があります。しかも、暴発しないぶん静かで、何週間も気付けない。
何が起こったか
自動購入の試験を走らせるうちに、前日の失敗を翌日の正午に再試行する仕組みが必要になりました。「明日の正午に再試行、明後日も、ずっと」——成功するまで、あるいは期限が来るまで毎日試す、という要件です。
ところが調べてみると、以前に使った使い捨てスクリプトの「自分で予約を消す」処理が、一度も動いたことがないことが分かりました。原因は直球すぎるほど直球で、スクリプトの途中に「ここで終了」という行が書いてあり、その後ろに片付け処理が置かれていたのです。終了行に到達した瞬間に処理は終わるので、後ろに何を書いても永遠に実行されない。消し忘れた予約が、日付指定つきの残骸として crontab に残り続けていました。
さらにその日はもう一つ学びがありました。正午の販売に間に合わせるための再試行設計が「失敗したら2分、4分、8分と間隔を空けて3回」という作りだったのですが、打ち切る時刻を計算してみると販売開始の2分前にリトライが終わる。待ち構えているはずの再試行が、肝心の瞬間より先に帰宅する設計だったのです。
何が変わったか
2つ直しました。
- 終了行を後ろに移動し、片付け処理が必ず通るように。さらに「消すのは成功したとき、または期限が過ぎたときだけ」に条件を変えました。毎日再試行が要件なのに「実行したらすぐ消える」設計では、失敗の翌日に何も残らないからです
- リトライの考え方を逆転し、「発売の2分前までに諦める」から「発売の12分後まで粘る」へ。失敗の間隔も、販売の前後で切り替える方式にしました
教訓は2つ。「終わったら片付ける、はコードのどこに置くかで永遠に実行されない」と、「再試行設計は、いつ諦めるかより先に、いつまで粘るかを決める」。どちらも地味ですが、動くものを“回し続ける”段階でしか顔を出さない種類のバグです。
<details><summary>▶ 技術的にどうなってるか</summary>
- 対象:
~/bin/mellojoy-qty2-test.sh(quantity=2カート投入の実走検証・system crontabで毎日発火) - 構造的バグ:
{ ... python3 ...; QTY_EXIT=$?; exit $QTY_EXIT; } >> logのブロック内にexitがあり、その後ろのcrontab -l | grep -v ... | crontab -(セルフ除去)が到達不能だった。修正:exitをブロック外末尾exit "${QTY_EXIT:-0}"へ移動 - セルフ除去の条件付き化:
verdict=okまたはTODAY>=20261015のときのみ除去・それ以外は「翌日11:57に再試行」を出して残置(verdictは実行結果JSONの最終要素から読む) - 発売ウィンドウ方式: 11:50以降の起動なら12:12:00まで粘る(12時前120秒/12時以降60秒間隔・上限20回)・429時は
retry-afterヘッダ+body冒頭200字を診断記録 - MLR r7〜r9(3ラウンド・計79指摘)で追加堅牢化: set -e下のexit捕捉・crontab空書込ガード(取得失敗→中止/他ジョブゼロ→正当な全除去)など
- 注意: 本話は2026-10-09〜11時点の記録。2026-10-13に同スクリプトはPlaywright実ブラウザ経路へ置換済み(第52話以降の候補)
</details>