WindowsのタスクスケジューラでClaude Codeを毎日自動で動かす
「毎朝、Notionのタスクを整理しておいてほしい」「毎晩、その日の記録をまとめておいてほしい」。Claude Codeに頼みたいことが決まってくると、毎回自分で起動して頼むのが面倒になってきます。Windowsなら、標準のタスクスケジューラで決まった時間に動かせます。
ただ、手で動かすと動くのに、自動にしたとたん動かない、ということがよく起きます。この記事では、私が毎日の自動実行を組んだときの形と、引っかかった所をまとめます。
自動実行の仕組み
- タスクスケジューラ決まった時間にPowerShellを起動する
- PowerShellのスクリプト作業フォルダに移り、claude -p を呼ぶ
- Claude Code頼んだ作業をして、結果を返す
- ログと終了コード結果をファイルに残し、失敗ならタスクも失敗にする
claude -p で、対話なしに1回だけ動かす
Claude Codeは、-p(--print)を付けると、対話の画面を開かずに1回だけ作業して終わります。公式ドキュメントでは、スクリプトやCIから使う方法として紹介されています。
claude -p "今日のタスクを整理して" --output-format json
押さえておきたい点は次の3つです。
- スキル(手順書)を呼べる。あらかじめ作っておいたスキルやカスタムコマンドは、
-p "/スキル名"の形で呼べます。長い指示を毎回書かずに済み、手で動かすときと同じ手順で動きます。 - 結果をJSONで受け取れる。
--output-format jsonにすると、結果の文章はresultに入り、total_cost_usdに費用の目安も入ります(公式ドキュメントでは、手元での見積もりで実際の請求とは異なることがあると説明されています)。 - 失敗すると0以外の終了コードで終わる。スクリプトの側で、成功か失敗かを判定できます。
なお、--bare を付けると起動が速くなりますが、CLAUDE.md やMCPの設定を読み込まず、普段のログインではなくAPIキーが必要になります。いつもの設定やNotion MCPをそのまま使いたいなら、付けずに動かします。
権限は「許可したものだけ」にする
自動実行のときは、確認を求められても答える人がいません。そこで、使ってよい道具を --allowedTools で並べ、それ以外は確認を出さずに断るモード --permission-mode dontAsk にしておきます。公式ドキュメントでは、dontAskは「確認が必要になる操作をすべて断る」モードで、--allowedTools で許可したものは動くと説明されています。
claude -p "/daily-tasks" `
--allowedTools "Read,Write,mcp__notion" `
--permission-mode dontAsk `
--output-format json
mcp__notion は、notion という名前で登録したMCPサーバーの道具をまとめて許可する書き方です。サーバーの名前は、Claude Codeの /mcp の一覧で確かめられます。何でも許可するのではなく、その作業に要るものだけを並べておけば、思わぬ操作を防げます。
PowerShellのスクリプトを用意する
タスクから直接 claude を呼ぶより、間にスクリプトをはさむほうが扱いやすくなります。次は、私が使っている形を簡単にしたものです。
run_daily.ps1(UTF-8のBOM付きで保存する)
# 毎朝タスクスケジューラから呼ばれる
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$ProjectDir = "C:\path\to\作業フォルダ"
$LogDir = Join-Path $ProjectDir "logs"
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
Set-Location $ProjectDir
# where.exe claude で調べた場所をフルパスで書く
$claude = "C:\path\to\claude.cmd"
$log = Join-Path $LogDir ("run_" + (Get-Date -Format "yyyy-MM-dd_HHmmss") + ".log")
$output = & $claude -p "/daily-tasks" `
--allowedTools "Read,Write,mcp__notion" `
--permission-mode dontAsk `
--output-format json 2>&1
$code = $LASTEXITCODE
$output | Out-File -FilePath $log -Encoding utf8
Add-Content -Path $log -Value "exit code: $code"
# これが無いと、claude が失敗してもタスクは「成功」に見える
exit $code
このスクリプトで気をつけた点は次のとおりです。
claudeはフルパスで呼ぶ。タスクスケジューラから動かしたときの環境は、普段のターミナルと同じとは限りません。PowerShellでwhere.exe claudeを実行すると場所が分かるので、それを書いておくと確実です。- 最後に
exit $codeを書く。これが無いと、スクリプトの終了コードは最後のコマンド(ログの書き込み)の結果になり、Claude Codeが失敗していてもタスクは成功扱いになります。私はこれで、失敗に気づくのが遅れたことがありました。 - 出力をUTF-8として受け取る。先頭の
[Console]::OutputEncodingの行が無いと、私の環境ではログの日本語が文字化けしました。
日本語を含むスクリプトはBOM付きで保存する
Windowsに最初から入っているPowerShell 5.1は、BOMの無いUTF-8のスクリプトを、古い「ANSI」の文字コードとして読みます。Microsoftの説明でも、ASCII以外の文字を使うならBOM付きのUTF-8で保存するよう書かれています。日本語のコメントや文字列を入れたスクリプトをBOM無しで保存すると、読み込みの段階でエラーになることがあります。メモ帳やVS Codeの保存時に「UTF-8(BOM付き)」を選んでください。
タスクを登録する
登録は画面からでもできますが、PowerShellで書いておくと、あとで設定を見直しやすくなります。
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\path\to\run_daily.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At 07:00
$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Hours 1)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive
Register-ScheduledTask -TaskName "DailyTasks" -Action $action -Trigger $trigger `
-Settings $settings -Principal $principal
| 設定 | 意味 | 付けた理由 |
|---|---|---|
| -ExecutionPolicy Bypass | このスクリプトの実行に限って、実行ポリシーの制限を外す | Windowsの既定の実行ポリシー(Restricted)では、スクリプトを実行できないため |
| -StartWhenAvailable | 予定の時刻を過ぎていても、あとで実行できる | その時刻にPCがスリープだった日も、起きたときに動かすため |
| -AllowStartIfOnBatteries | バッテリー駆動中でも開始する | 既定では、バッテリー駆動中は開始しない設定になっているため |
| -DontStopIfGoingOnBatteries | 途中で電源が外れても止めない | 作業の途中で止まると、中途半端な状態が残るため |
| -MultipleInstances IgnoreNew | 前の回がまだ動いていたら、新しい回は始めない | 同じ作業が重なって二重に書き込むのを防ぐため |
| -ExecutionTimeLimit | 実行時間の上限 | 指定しないと上限は3日。止まったまま残らないよう短くしておく |
ノートPCは、バッテリーの設定で止まりやすい
Microsoftの説明では、タスクの「バッテリー駆動中は開始しない」という設定は、既定で有効です。私はノートPCを持ち歩いて使っているので、この設定のままだと、電源につないでいない日はタスクが待ったまま動きませんでした。手元では動くのに自動だと動かない、という場合は、まずこの設定を疑ってみてください。画面からなら、タスクのプロパティの「条件」タブで確認できます。
費用と、動かさない日の作り方
Claude Codeは、1回動かすたびに使える量(または費用)を消費します。私の環境では、Notionを読み書きする程度の作業で、JSONの total_cost_usd が1回あたりおおむね0.5〜1ドルでした。毎日、やることが無い日まで起動していると、それなりの量になります。
そこで、Claude Codeを呼ぶ前に、PowerShellやPythonの小さな処理で「今日はやることがあるか」を確かめ、無ければ起動せずに終わるようにしています。たとえば、Notionに新しい依頼が無ければ終わる、取り込むファイルが無ければ終わる、といった判定です。AIに頼まなくても判断できることは、手前で済ませておくのが節約のこつです。
まとめ
claude -pで、対話なしに1回だけ動かす。スキルを呼べば、手で動かすときと同じ手順になる- 権限は
--allowedToolsと--permission-mode dontAskで、要るものだけに絞る - 間にPowerShellのスクリプトをはさみ、
claudeはフルパスで呼び、最後にexit $codeで失敗を返す - 日本語を含むスクリプトはBOM付きのUTF-8で保存する
- ノートPCは
-AllowStartIfOnBatteriesを付けないと、電源につないでいない日に動かない
Pythonのスクリプトも一緒に自動で動かす場合は、仮想環境(venv)の置き場所にも注意が要ります。OneDriveの中にPythonの仮想環境を置くと、自動実行で壊れることがあるで、私が実際にはまった例を紹介しています。