夜間自動化は「作れた=安定運用できる」ではない

夜間バッチ処理のログ画面とバックアップ完了を示すノートPC。夜間自動化の運用設計をテーマにしたアイキャッチ画像 ノマドワークフロー

毎晩23時30分に動かしている自動バックアップが、ある日、途中で止まりました。

調べていくと、プログラムの不具合ではありませんでした。その時間に作業していた私が、画面に出ていたウィンドウを閉じたことが引き金でした。

この話を書き残しておこうと思ったのは、自動化の仕組みは「作れたら終わり」ではないと、あらためて思い知らされたからです。人がいない時間に、人が見ていない状態でも動き続けるところまで設計して、はじめて業務に組み込めます。動くものを作った時点は、まだ途中です。

技術的な話が中心になりますが、AIやツールを業務へ入れようとしている方に共通する話だと思っています。夜間や休日に処理を任せる仕組みは、どんな道具を使っていても同じ問題を抱えるからです。

何が起きたか

毎日23時30分に、作業データのバックアップを自動で取っています。ローカルに保存したあと、クラウドストレージへ転送する、という2段構えです。

ある日の分を確認すると、こうなっていました。

  • ローカルへの保存は完了している
  • クラウドへの転送は1044ファイルで途切れている(本来は1152ファイル)
  • 終了コードは 3221225786(16進数で 0xC000013A

この終了コードは、Ctrl+Cやコンソールの終了など、処理が外側から中断されたときに見られるものです。ただし、これ単独で「人がウィンドウを閉じた」と決まるわけではありません。

原因を絞り込めたのは、次の4つが揃ったからでした。

  • 外部からの中断を示す終了コードが残っていた
  • バックアップが起動したのと同じ秒に、ターミナルアプリが終了した記録があった
  • シャットダウン・スリープの記録はなかった
  • 後述する再現テストで、同じ壊れ方を再現できた

その時間帯、私は別の検証作業をしていて、画面に出ていたウィンドウをいくつか閉じていました。

原因は「閉じられる窓が出ていたこと」だった

Windowsには「タスクスケジューラ」という、決まった時刻に処理を動かす標準機能があります。今回もこれを使っていました。

ここに落とし穴がありました。今回の環境では、タスクスケジューラから起動した処理が、Windows Terminal のウィンドウとしてデスクトップに表示される状態になっていました。(この挙動はWindowsのバージョンや設定によって変わります。すべての環境で同じとは限りません。)

そして、そのウィンドウを閉じると、中で動いていた処理ごと止まります。

普段この時間にパソコンの前にいなければ、窓は数十秒で勝手に消えるので、誰も気づきません。「たまたまその時間に作業していた」という条件が揃ったときだけ発生する——見つけにくい種類の問題でした。

推測で直さず、まず同じ壊れ方を再現した

原因の見当がついた時点で、すぐ対策を入れることもできました。ただ、そうしませんでした。

先に、わざと同じ状況を作って壊しました。

  • バックアップを実行する
  • 処理の途中で、出ているウィンドウを強制終了する
  • 終了コードを確認する

結果、事故のときと同じ 3221225786 で中断しました。

この一手間には、3つの意味がありました。

  • 推測していた原因が、本当に合っているか確かめられる
  • 対策後に同じ操作をして、今度は止まらないところまで検証できる
  • 「直ったつもり」で終わらせずに済む

今回のような不具合では、再現できると対策の効果まで確認しやすくなります。再現しないまま直していたら、「本当に直ったのか」を判断しにくい状態が残っていました。

効かなかった対策 3つ

「ウィンドウを出さなければいい」——方針は単純です。ただ、思いついた方法の多くは効きませんでした。記録として残しておきます。

試したこと結果
Windowsの設定を変更し、旧来のコンソールを使うよう指定する手動で起動したときは効いたが、今回の環境ではタスクスケジューラ経由だと期待した状態にならなかった
非表示で起動する専用のランチャーを噛ませるタスク経由だとランチャーが即座に終了してしまい、本体が動かなかった
「ウィンドウを隠す」オプションを付けて起動するウィンドウは出たまま。再現テストで同じように中断した

3つ目が特に厄介でした。オプションの名前は「隠す」ですが、この環境では隠れませんでした。

ここから得た教訓は、設定の名前や「こう動くはず」という想定ではなく、実際の挙動を自分の環境で確認しないといけないということです。名前のとおりに動くとは限りません。

再現テストをしていなければ、この3つはすべて「効いたつもり」で終わっていました。設定した安心感だけが残って、事故は再発していたはずです。

採用した設計

1. 窓を隠すのではなく、そもそも持たない

効かなかった対策は、どれも「出てくる窓を、あとから隠す」という発想でした。窓は存在していて、見えなくしようとしていただけです。

変えたのは、最初から画面を持たない方式で起動することです。処理そのものは何も変えていません。起動のしかただけを変えました。

人が閉じられる窓が存在しなければ、閉じられて止まることもありません。「隠す」と「持たない」は、言葉は似ていますが、人の操作に巻き込まれるかどうかという点でまったく違う設計です。

2. 二重起動の防止は、「今も動いているか」で判断する

あとで触れる「朝に自動で流し直す仕組み」を入れたことで、夜の処理と朝の処理がぶつかる可能性が出てきました。同じ処理が二重に走ると、ファイルの取り合いになります。

よく見かけるのは「30分以上前の記録なら、もう終わっているだろうとみなす」という時間での判定です。これは危ないと考えました。大量のファイルを転送していれば、30分を超えることは普通にあります。まだ動いている処理を、時間だけを理由に横から止めてしまいます。

そこで、判断の基準を時間から「その処理が今も動いているか」に変えました。

  • 実行中の処理の番号(プロセスID)と、始まった時刻を記録しておく
  • 次の起動時に、それが今も生きているかをWindowsに問い合わせる
  • 生きている → 経過時間に関係なく待つ
  • 終わっている → 経過時間に関係なく引き継いで実行する
  • 番号が同じでも始まった時刻が違えば、それは別の処理(番号は使い回されることがある)

ここで時間は使いません。時間を持ち出すのは、例外のときだけです。

権限などの都合で、OSに問い合わせても生死が判定できないことがあります。そのときは「動いている側」に倒して待ちます。ただしそれだけだと、記録が壊れていた場合などに永久に待ち続けてしまうので、一定時間を超えたものは残骸とみなして引き継ぎます。

つまり時間は、通常の判定基準ではなく、状態を確認できないときに止まりっぱなしを防ぐための最後の逃げ道という位置づけです。

3. 「正常終了」だけを見て、成功したと判断しない

これが今回いちばん学びになった点です。

上の仕組みでは、「すでに動いていたので今回は何もしなかった」というときも、正常終了として返します。ぶつからずに譲ったのですから、異常ではありません。

ところがこれをそのまま受け取ると、こうなります。

  • 実際に処理して完了した → 正常終了
  • 別の処理が動いていたので何もしなかった → 正常終了

やったこととやらなかったことが、同じ顔をして並びます。これを見て「今日も無事に終わった」と判断すると、実際にはバックアップが取られていない日を見逃します。

そこで、結果を成功・失敗の2つではなく、「何が起きたか」という状態で別に記録するようにしました。

  • 実行した
  • すでに動いていたので、何もしなかった
  • 準備の段階で失敗した

自動化の結果を〇か×かで持つと、「やらなかった」が「うまくいった」に紛れ込みます。これは、たいていの自動化に当てはまる話だと思います。

4. 失敗したときの扱いまで決めておく

ここまでで今回の事故は防げます。ただ、別の理由で失敗する夜は、これからもあり得ます。失敗をゼロにはできません。

そこで、毎朝実行している確認処理に、前夜のバックアップが終わっているかを見て、終わっていなければ自動で流し直す仕組みを組み込みました。すでに終わっていれば何もしません。何度実行しても結果が変わらないようにしてあります。

結果は4つの状態で報告されます。

状態意味
正常前夜のうちに終わっていた。何もしていない
埋め直した終わっていなかったので、朝に流し直して完了させた
実行中別の処理が動いていたので重複起動しなかった(異常ではない)
失敗流し直したが完了しなかった。人が確認する

作ったのは「失敗しない仕組み」ではありません。失敗しても、人が気づく前に復旧を試みて、人の判断が要るときだけ上げてくる仕組みです。

前回の事故も、私が終了コードを見に行かなければ気づかないままでした。「気づいたら直す」を人間の役割から外したことが、この節の要点です。


対策後、実際にどうなったか

対策を入れたのは2026年8月19日です。そこから8月25日までの7夜、毎晩23時30分の処理がどうなったか、記録を1夜ずつ確認しました。

結果は7夜とも完了。途中で止まった夜はありませんでした。

保存したファイル数容量手元とクラウドの一致
1夜目1,26626.42MB一致
2夜目1,26926.46MB一致
3夜目1,29126.68MB一致
4夜目1,33428.33MB一致
5夜目1,33528.33MB一致
6夜目1,34828.58MB一致
7夜目1,35628.85MB一致

ただし1夜目は対策を入れた当日で、動作確認のための実行も含んでいます。手を触れずに回った夜として数えるなら、2夜目から7夜目までの6夜というのが正確なところです。

最後の夜の所要時間は約4秒でした。

ただし、確かめられなかったことが2つあります

確認したかったのは、実は完了した夜数だけではありませんでした。

  • 二重起動を防ぐ仕組みが、実際に働いた場面はあったのか
  • 朝の自動補完が「埋め直した」を返した日はあったのか

どちらも、答えられませんでした。

理由は単純で、結果を記録するファイルが、最新の1回分しか残らない作りだったからです。前の晩の結果は、次の晩に上書きされて消えます。

分かったのは、確認できた範囲だけです。最後の夜は「実行した」、その翌朝と翌々朝はどちらも「正常(前夜のうちに終わっていたので何もしていない)」。それ以外の日に何が起きていたかは、いま調べても分かりません。

いちばんの収穫は、直ったことではなかった

7夜連続で動いた、という事実は確かに安心材料です。ただ、この記事の出発点は「止まっていることに15日間気づけなかった」でした。

その視点で見ると、いまの状態はまだ半分です。止まったことには気づける。でも、途中で何が起きていたかは残っていない。

自動化の運用でほんとうに必要なのは、「失敗を見つける仕組み」だけではありません。あとから「ちゃんと動いていた」と証明できる形で残っていることです。今回それが足りていないと分かったこと自体が、7夜の実測でいちばん価値のある結果でした。

次に直すのは、そこです。


これはバックアップだけの話ではなかった

対策を終えてから気づいたのですが、同じ構造の詰まりが、別のところでも起きていました。

書き上がっていた記事が、公開する操作だけが人に依存していたために、何日も出ないまま止まっていたということがありました。原稿はできている。あとは出すだけ。それなのに進んでいませんでした。

書く工程で詰まったのではなく、出す工程で詰まっていたわけです。

バックアップの件と、共通点があります。どちらも人がその日その場で動かないと進まない設計になっていました。忙しい日に当たれば、落ちます。落ちても、しばらく誰も気づきません。

そこから、自分の中で線引きがはっきりしました。

人が判断する工程は残す。人が覚えていてボタンを押す工程は減らす。

これは「判断は自動化してはいけない」という話ではありません。技術的には、判断を任せられる領域は広がっています。

そのうえで、私たちが自社の運用で置いている基準は、判断が必要なところまで自動で材料を揃え、最終判断は人が行うという形です。バックアップが失敗したときに「これは放置していいのか、すぐ手を打つべきか」を決めるのは人の側に残す。ただし、そこに至るまでの確認・復旧の試行・状態の整理は自動で済ませておく。

人に残すのは判断であって、作業の記憶ではありません。

AIを業務に入れようとしている方へ

AIエージェントに夜間作業をさせる、という話をよく聞くようになりました。実際に私も動かしています。

ただ、今回止まったのはAIの判断ではなく、それを動かしている土台の部分でした。AIがどれだけ賢くても、起動する仕組みが人の操作に巻き込まれて止まるなら、業務にはのせられません。

安定して回すために必要だったのは、AIの性能ではなく、こういうことでした。

  • 人の操作に巻き込まれない起動方式にする
  • 二重に走らない仕組みを、正しい判定基準で作る
  • 結果を、成功・失敗の2つではなく状態で残す
  • 失敗したときの扱いまで決めておく
  • 対策を入れたら、実際に壊してみて確かめる

どれも、システム設計の世界で以前から言われてきたことです。新しい話は一つもありません。

それでも、AIを人が見ていない時間にも動かすようになるほど、この土台の重みは増していくと感じています。任せる範囲が広がるということは、人が見ていない時間が長くなるということだからです。

「作れた」と「毎晩動き続ける」の間には、まだ距離があります。その距離を埋める作業が、業務で使える自動化と、動かしてみただけの自動化を分けているのだと思います。

コメント