この記事で切り分けられること
Windows のタスクスケジューラで動かしている bat が、特定のタイミングから終了コード2(LastTaskResult=2)で落ちるようになった場合の切り分け方を書く。本体のスクリプトは動いているのに bat 全体は失敗する、というパターンが対象になる。
症状
営業の定時タスク outreach_daily の LastTaskResult が 2 になっていた。10/27 0:49 の実行も失敗している。タスク本体の処理(run_outreach.py daily)は正常に動いていたため、タスク全体が失敗していることに気づくのが遅れた。
気づけたきっかけは、本人からの「他の作業が止まっていないか」という確認で、同じホストで動いている他の予定タスクも含めて LastTaskResult を全件確認したことだった。
Get-ScheduledTask | Select-Object TaskName, @{N='LastRunTime';E={$_.LastRunTime}}, @{N='LastTaskResult';E={(Get-ScheduledTaskInfo $_.TaskPath$_.TaskName).LastTaskResult}}
原因
scripts/outreach_daily.bat の12行目の中身を調べたところ、py -3 scripts の後に見た目では分からない CR(0x0D)が1文字挟まり、続けて un_ai_gig_watch.py という文字列になっていた。つまり本来 scripts\run_ai_gig_watch.py と書いたつもりの行の、バックスラッシュ(0x5C)の位置が復帰文字(0x0D)に化けていた。
Python 側はそのパスのファイルが存在しないため終了コード2を返し、bat 全体がその終了コードを引き継いで2で終わっていた。営業本体のスクリプトは別行で呼ばれており、そちらは正しく動作していたため、気づくまで時間がかかった。
直し方
- 対象の bat の該当行をバイト単位で読み、怪しい位置に 0x0D があるかを確認する。
- 見つかった 0x0D を本来のバックスラッシュ 0x5C に置換する。
- 置換後、該当の Python スクリプトを単体実行して終了コードが0になることを確認する。
py -3 -c "print([hex(c) for c in open(r'C:/Users/<あなたのユーザー名>/scripts/outreach_daily.bat','rb').read()[i:j]])"
i, j には該当行の先頭と末尾あたりのバイト位置を入れて、範囲を少しずつ狭めながら確認する。0x0D が見つかったら、エディタ(Write や Edit など)でその行を書き直し、再度保存する。
py -3 scripts\run_ai_gig_watch.py
修正後は run_ai_gig_watch.py 単体で終了コード0になることを確認してから、bat 全体をもう一度実行して LastTaskResult が0になることまで見る。
それでも駄目なときに次に見る場所
- bat や ps1 にパスを書いた直後は、
cat -Aではなくバイト値で確認する。cat -Aは行の途中に挟まった CR を^Mとして表示しないことがあるため、見た目上は気づけない場合がある。 - パスや正規表現をヒアドキュメントで書くと、改行コードの変換によって同じ現象が起きやすい。Write や Edit など、バイト列をそのまま扱える方法で書く。
- 同じ型の文字化けは過去にも発生しており(P78、P256)、bat や ps1 にパスを書く作業全般で再発しやすい箇所になっている。新しくパスを書いたファイルは、保存直後にバイト単位での確認を一度挟む。

Claude Codeとは何か、実際に投げた依頼文と直し方から説明する
Notion API連携の最初の設定をインテグレーション作成から実際に動かす手順
CSVの文字化けと列ずれが止まる場所をPythonのコードで具体的に潰す手順