Claude Codeのタスクスケジューラ実行で拒否される原因と切り分け
Windowsのタスクスケジューラからclaude -pを動かすと、allowlist済みのツールがrequires approvalで拒否される。issue #99795の症状と、公式仕様から確認できる切り分け手順をまとめます。
Windowsのタスクスケジューラからclaude -pを動かすと、--allowedToolsに入れたはずのBashやEditが「requires approval」で拒否される。同じコマンドを対話シェルで打つと通る。この食い違いが、GitHubのissue #99795で報告されています。issueはopenで、原因は特定されていません。
先に前提を置きます。タスクスケジューラが原因だと確定した情報はありません。一方で、公式ドキュメントから「無人実行で許可が外れやすい箇所」は読み取れます。この記事では、issueの症状、報告者がすでに潰した仮説、公式仕様で確認できる点の順に並べ、最後に再現ログを取るPowerShellを置きます。
issue #99795で報告されている症状
報告者の環境は、Windows 11 HomeとClaude Code 2.1.278です。タスクスケジューラのタスクとしてpowershell.exe -File run_loop_batch_headless.ps1を起動し、プリンシパルはInteractiveToken、アクションには作業ディレクトリを明示しています。スクリプトの中身は次のコマンドです。
claude -p "/loop-batch" --append-system-prompt <text> `
--allowedTools "Bash PowerShell Read Write Edit Glob Grep Skill WebSearch WebFetch mcp__playwright PushNotification" `
--output-format json症状は、Bash・PowerShell・Write・Editのどの呼び出しも「requires approval」として拒否されることです。許可は--allowedToolsと、プロジェクト側のpermissionsおよびautoModeのallowルールの両方に書いてあります。セッションは約4秒で終わり、コストは0ドル・トークンは0でした。
経緯は次のとおりです。
| 日付 | 状態 |
|---|---|
| 2026-09-27 22:00 | 状態最後に正常終了した実行(実コスト約40ドル) |
| 2026-09-28 22:00 | 状態初めて失敗。以後、夜間1回と日中の毎時実行がすべて同じ症状 |
| 2026-10-05 | 状態同じ日の連続約15回の毎時実行が、すべて同じ症状 |
報告者が確認した事実も3つあります。直近のCLI更新は9月22日で、失敗の6日前です。.claude/settings.local.jsonのautoModeルールは、失敗の境目で変わっていません(gitの履歴で確認)。そして、同じコマンド・同じ--allowedTools・同じ環境変数を対話セッションから手で実行すると、何度試しても拒否されません。
画面ロックの有無、時間帯、バッテリーかACかは、失敗と相関しなかったと書かれています。--permission-prompts noneはこの時点では渡していません。
コメントで出た仮説と、報告者の否定
別の利用者(runvouch)が10月6日に、起動前に落ちている可能性を指摘しました。トークン0・4秒終了なら、APIを一度も呼んでいない。ツールが途中で拒否されたのではなく、最初のターンの前に死んでいるという読みです。挙げられた確認先は3つです。
--append-system-promptの値がスケジューラ配下で空になり、直後の--allowedToolsがその値として食われていないか- 引数のクォートが、手入力のコマンドと同じ文字列に展開されているか
- スケジューラ配下のPATHが、別の
claudeを解決していないか
報告者(LittleMansFactory)は10月8日に、3つとも自分の環境では当てはまらないと回答しています。システムプロンプトの値はスクリプトに直書きの文字列で、引数のエスケープは標準的な方式で組んであり、起動は%APPDATA%\npm\claude.cmdの絶対パスです。プリンシパルもSYSTEMではなく、同じ対話ユーザーでした。
加えて、現在の失敗は「4秒終了」とは違う形だとも述べています。同日の失敗は238秒と641秒かかってからエラー判定を返し、トークンは0のままでした。つまり引数の解析クラッシュより、ローカルでツール呼び出しが止められている状況に近い、という見立てです。
ここで、読む側が持っておくべき線引きがあります。runvouchの3項目は、自分の環境の切り分けとしてはそのまま使えます。ただし、このissueの報告者については否定済みです。
10月12日の追加観察: 固定の設定ミスではない可能性
報告者は10月12日に、調べた結果を更新しています。1つの無人タスクを、調査だけを行う長いセッションと、書き込みだけを行う短いclaude -pに分けたところ、拒否は減りましたがゼロにはなりませんでした。
- 同じファイル・同じ内容・同じ起動スクリプトの書き込みが、新しいプロセスごとに成功したり失敗したりする。書き込み専用セッションを5回連続で作り直したところ、5回とも最初のEditで拒否された。別のタスクでは、前日に同種のセッションが初回で成功している
- スケジューラもヘッドレスも介さない対話セッションでも、
.claude/hooks/配下の自作フックのEditが2回続けて「auto modeの分類器」に拒否され、バイト単位で同一の入力の3回目で通った
報告者はこの2点から、タスクスケジューラ固有の問題ではなく、auto modeの分類器による拒否が無人実行で詰まる別の報告群(#87809、#97911)と同じ系統だと見ています。ローカルの原因探しは打ち切り、書き込み専用ステップを新しいプロセスで数回リトライし、それでも拒否されたら人間に引き継ぐフラグを立てる迂回策を作ったとのことです。
この見立ても、報告者の推測であり、Anthropic側の確認ではありません。記事としては「症状は再現条件が固定でなく、原因は未確定」と受け取るのが安全です。
公式仕様から確認できる4つのポイント
issueの原因が不明でも、公式ドキュメントに書かれた無人実行の挙動を順に当てれば、自分の環境で潰せる項目は残ります。
1. -pの開始モードがautoになることがある
claude -pでモードを指定しないと、組み込みの開始モードが使われます。公式の表では、-pは機能フラグを取得するセッションではdefaultです。フラグを取得しないセッション(サードパーティプロバイダーやテレメトリー無効など)では、v2.1.285以降ならauto、それ以前はdefaultになります。
つまり、対話シェルと無人実行で、フラグ取得の可否や環境変数が違えば、開始モードが違う可能性があります。issueの報告者はautoModeのallowルールを使っており、10月12日のコメントでは--permission-mode auto --permission-prompts noneで動かしています。モードを毎回明示して、環境差を消すのが第一手です。
2. autoでは素のBashがallowから外される
ここが見落としやすい点です。--allowedToolsにBashだけを書いた場合、auto modeで始まった実行では、Claude Codeがその素のBashを広すぎるallowルールとして落とし、各コマンドを分類器で判定します。Read・Editのような他のツールは、この記述にかかりません。
issueのコマンドはBash PowerShell Write Editのように素のツール名を並べています。報告者の対話実行がなぜ通るのかは、この記述だけでは説明できません。しかし、開始モードがautoだったときに、素のBashが分類器の判定に回る、という公式の挙動とは符合します。原因だと言い切るのは避けますが、確認する価値のある項目です。
素のBashを使わずに、実際に使うコマンドをパターンで書く方法があります。公式のCLIリファレンスの例は、"Bash(git log *)" "Bash(git diff *)" "Read"の形です。
claude -p "テストを実行して失敗を直して" `
--permission-mode dontAsk `
--allowedTools "Bash(npm test *)" "Read" "Edit"3. dontAskは拒否を決まった動きにする
dontAskは、確認になるはずの呼び出しをすべて拒否するモードです。公式の記述では、Manualで承認不要な操作(作業ディレクトリ内の読み取りや読み取り専用コマンド)と、--allowedToolsまたはpermissions.allowが当たる操作は実行されます。
無人実行で「許可したものだけが動き、それ以外は拒否」と決めたいときの選択肢です。詳しい設定はdontAskモードの解説にまとめています。ただし、dontAskがissueの症状を直すという確認はありません。分類器の判定が混ざらない形にして、拒否の理由を読みやすくする手段、という位置づけです。
4. 拒否を黙って落とさず記録する
--permission-prompts noneは、誰も答えられない場面向けのフラグで、v2.1.259以降で使えます。確認になるはずの要求は拒否され、Claudeには「承認できる人がいないので再試行しないこと」が伝わります。許可ルール・PermissionRequestフック・指定したモードは、これまでどおり先に判定されます。
さらに、--output-format stream-json(-pでは--verboseと組で指定します)を使うと、拒否はpermission_deniedのシステムメッセージとして出力されます。最終結果のメッセージにはpermission_denialsが一覧で入ります。issueのコマンドは--output-format jsonです。ログに残すなら、どのツール呼び出しが拒否されたかをstream-jsonで取っておくと、拒否の形が固定かばらつくかを後から比べられます。
設定スコープ・実行ユーザー・作業ディレクトリの確認
issueの題材はタスクスケジューラですが、確認項目はWindowsのどの自動実行にも使えます。次の3つが、対話実行と無人実行の差が出やすい場所です。
| 確認する場所 | 公式仕様 | 無人実行で見ること |
|---|---|---|
| ユーザー設定 | 公式仕様~/.claude/settings.json(ホームの.claude) | 無人実行で見ることタスクの実行ユーザーが対話ユーザーと同じか |
| プロジェクト設定 | 公式仕様.claude/settings.jsonと.claude/settings.local.json(作業ディレクトリ側) | 無人実行で見ることタスクの「開始場所」が、設定を置いたプロジェクトになっているか |
| 優先順位 | 公式仕様管理設定、コマンドライン、ローカル、共有プロジェクト、ユーザーの順 | 無人実行で見ること--settingsで渡した値が、ファイルの同じキーを上書きしていないか |
-pは、--bareを付けない限り、対話セッションと同じ文脈(作業ディレクトリと~/.claudeの設定を含む)を読み込みます。作業ディレクトリがずれていれば、プロジェクトのpermissionsも読まれません。issueの報告者は作業ディレクトリを明示していますが、自分の環境では「開始場所」の空欄や誤りが差の候補になります。
.claude/settings.local.jsonはgitに入らないのが通常なので、別のクローンやCI用のチェックアウトには存在しません。「手元では通るのにタスクでは通らない」の原因が、別ディレクトリのチェックアウトを見ているだけ、という例は十分ありえます。
もう1つ、ルールの評価順です。ルールはdeny、ask、allowの順に評価され、最初に当たったものが結果を決めます。allowを足しても、同じ呼び出しに当たるdenyやaskが別のスコープにあれば、通りません。プロジェクト設定に加えてユーザー設定や管理設定も見てください。
再現ログを取るPowerShell
runvouchが勧めた「実際に渡した引数と解決先を残す」確認を、スクリプトに組み込む例です。claudeを呼ぶ直前に、次の情報をファイルに出します。
$log = "C:\ops\claude-task\diag-$(Get-Date -Format yyyyMMdd-HHmmss).txt"
@(
"user: $env:USERNAME"
"cwd: $(Get-Location)"
"claude: $((Get-Command claude).Source)"
"ver: $(claude --version)"
) | Out-File $log -Encoding utf8
claude -p "ツール呼び出しを1回だけ行って" `
--permission-mode dontAsk `
--allowedTools "Read" `
--output-format stream-json `
--verbose |
Out-File $log -Append -Encoding utf8同じスクリプトを、タスクスケジューラの実行と手動実行で1回ずつ回し、ログの冒頭4行を比べます。ユーザー・作業ディレクトリ・実行ファイル・バージョンのどれかが違えば、それが次の調査対象です。4行が一致して、それでもpermission_deniedの内容だけが違うなら、issueの報告と同じ状態にいる、ということになります。
回避策: 拒否を前提にした設計
ここまでで直らない場合は、報告者が採った設計が参考になります。
- 調査と書き込みを別のセッションに分け、書き込みは短い新規プロセスにする
- 書き込み専用ステップは数回リトライし、毎回の結果を記録する
- 規定回数で失敗したら、サイレントに諦めず、人が対話セッションで仕上げる対象としてフラグを立てる
もう1つの切り分けとして、認証の失敗も無人実行では似た見え方をします。ただしこの場合のエラー文言は別で、claude -pのOAuth session expiredのように、原因が文字列で分かります。トークンが0で終わる点だけを見て、認証と許可を混同しないようにします。
MCPツールの呼び出しが expected: "nonoptional" の入力検証エラーで止まる場合は、許可ルールとは別の原因です。Claude DesktopのCodeタブで任意パラメータを省略したときの不具合については、MCPの任意パラメータ省略に原因と回避策があります。
結論
issue #99795は、再現条件も原因もまだ固まっていません。確実に言えるのは次の3点です。
- 症状は、対話実行で通るallowlistが、タスクスケジューラ起動の
claude -pで拒否されること - 報告者は、引数の空展開・クォート・別のclaudeの解決・実行アカウントの4つを潰しており、固定の設定ミスでは説明しにくい
- 10月12日時点の観察では、同一入力でも新しいプロセスごとに結果が割れる
手元で試す順序は、モードの明示、素のBashをやめてパターン指定にする、stream-jsonでpermission_denialsを残す、の3つです。それでも拒否されるなら、ログを添えてissueにコメントする価値があります。ユーザー・作業ディレクトリ・実行ファイル・バージョンの4行が揃っていれば、報告として使えます。