同じ落とし穴にどうして3回もはまるのか:自動解析プラグイン反復修正の実録(体系的な振り返り付き)

「同じ落とし穴で、なぜ3回も転ぶのか?——zidongjiexi 反復修復の実録(体系的な教訓付き)」

ある種のバグは思春期みたいなものだ。終わったと思ったら、髪型を変えてまた戻ってくる。
今回の astrbot_plugin_zidongjiexiplusyoutubexiazai の不具合はまさに典型で、毎回「直ったように見える」のに、次の再起動でまた派手に転ぶ


一、事故タイムライン:「PIL エラー」から「環境互換性」へ

フェーズ 1:初回のエラー(リソース読み込みでクラッシュ)

プラグイン起動時、render.py_load_video_button で発火:

  • img.convert("RGBA")
  • Pillow 内部の AssertionError

直観的な結論:media_button.png のリソースデコードに地雷がある(画像自体の異常の可能性もあれば、実行時のデコード経路の差異の可能性もある)。

フェーズ 2:1回目の修復(成功したように見える)

リソースを(RGBA で)再保存したところ、その場の再起動では動いた
そのため「問題は解決した」と結論づけやすい。

フェーズ 3:再発(同じ箇所で再びクラッシュ)

その後の再起動でまた落ちた。つまり問題は「一度だけファイルが壊れていた」ではなく:

  • そのコードパス自体にフォールトトレランスがない
  • プラグインが異なる実行状態になると、同種のデコード失敗を引き起こし得る

フェーズ 4:2回目の修復で「修正が新たな問題を導入」

パッチ適用中に、典型的な二次事故が2種類発生:

  1. 構文レベルの混入(コメント/文字列処理の不備)で SyntaxError を引き起こす
  2. 環境ライブラリのバージョン差により EmojiCDNSource の引数が非互換(enable_tqdm が未対応)

最終的に、安定版ファイルへのロールバック + 最小増分の変更 + コンパイル検証 + 再起動検証で収束。


二、根本原因分析:なぜ「直したのにまた壊れる」のか

1)「一度成功」は「体系的修復」ではない

壊れた画像を再保存するのは、その場のサンプルを消しただけで、将来また同種のパスが発火しないことを保証しない。

2)プラグインコードに重要リソースのフォールバック戦略がない

リソースは外部依存だ。外部依存は「失敗しても致命傷にならない」べきである。

3)実行環境は不変ではない

同じプラグインでも、異なるコンテナイメージや依存バージョンでは初期化挙動が変わり得る。
今回の apilmoji の引数互換性差は、業務ロジックの誤りではなく、エコシステムのバージョン結合である。

4)修復プロセスに「強制ゲート」がない

パッチごとに次の強制検証を即時にやらないと、「動く状態」を「構文エラー状態」に簡単に戻してしまう:

  • py_compile の構文チェック
  • 最小コールドスタート検証
  • 重要プラグイン読み込みログのアサーション

三、今回の最終修復でやったこと

zidongjiexi

  1. _load_video_button にフォールトトレランスを追加:
    • img.load() で事前に強制デコード
    • 例外時は透明プレースホルダー画像に降格し、プラグイン全体の起動失敗を回避
  2. EmojiCDNSource の非互換引数 enable_tqdm を除去し、現行実行環境に適合
  3. 複数回の再起動で検証し、プラグインが安定して「読み込み済み」状態に入ることを確認

memelite(ついでに修正)

  • libEGL.so.1 欠如を修正:libegl1/libgl1 をインストールし、起動期の依存復旧ループを解消

四、なぜ再起動が 20 秒から 600+ 秒に変わったのか

「マシンが急に遅くなった」のではなく、起動チェーンにブロッキング要因が増えた:

  1. memelite の依存復旧の反復 + pip プロセスで起動が長引く
  2. プラグインの読み込み失敗の反復が全体初期化時間を増やす
  3. エラーログ嵐(第三者ログのフォーマット異常を含む)が体感の詰まりをさらに増幅

平たく言うと:AstrBot が一夜で老けたのではなく、起きながらタイヤを直している。


五、工学的教訓(バグ修正より価値がある)

教訓 1:リソース読み込みは「失敗しても生存」できなければならない

画像、フォント、外部 CDN、Cookie ファイル……常に健全だと仮定してはいけない。
耐障害性 + 降格が、プラグイン保守性の最低ラインである。

教訓 2:修正は「最小変更の閉ループ」で回す

推奨の固定手順:

  1. 既知の安定ベースラインに戻す
  2. 変更は1箇所だけ
  3. 構文/単点検証
  4. 再起動で検証
  5. 次の1箇所へ

教訓 3:「直った」は再現可能な検証を含めなければならない

「こっちでは直った」には意味がない。再起動を跨ぎ、時間を跨いでも「まだ直っている」を再現できなければならない。

教訓 4:ログを書くときは全体フォーマット結合に注意

サードパーティのログフィールドが自システムのログ形式と合わないと、ノイズになったり診断を妨害したりし得る。
サードパーティ logger に専用 handler を付ける、あるいはログレベルを下げることを検討するとよい。


六、今後の提案(次の再演を防ぐ)

  1. プラグインに起動時セルフチェックコマンドを追加(リソースファイル、依存バージョン、重要設定を検査)
  2. CI またはリリーススクリプトに追加:
    • 構文チェック
    • 最小 import テスト
    • 重要初期化関数の smoke test
  3. 「非コア機能リソース」は統一して降格戦略を採用
  4. 「再起動所要時間の警告しきい値」(例:>90s で通知)を設け、遅い箇所のログを自動収集

結語

今回の事故で最も価値があるのは「ついに直った」ことではなく、次を確認できたことだ:

  • 本当に納品できる修正とは、一度きりの手作業の消火ではない
  • システムを「運で起動する」から「予測可能に起動する」へ変えることだ

バグが知能税を取りに来るのだとしたら、今回は少なくとも領収書を残せた。:slightly_smiling_face: