Headless (-p) sessions launched via Windows Task Scheduler deny already-allowlisted tool calls (Bash/PowerShell/Write/Edit); identical manual invocation never fails
Environment: Windows 11 Home, Claude Code 2.1.278, launched via powershell.exe -File run_loop_batch_headless.ps1 as a Windows Task Scheduler task (Principal LogonType=InteractiveToken, explicit WorkingDirectory set in the task action).
Command:
claude -p "/loop-batch" --append-system-prompt <text> --allowedTools "Bash PowerShell Read Write Edit Glob Grep Skill WebSearch WebFetch mcp__playwright mcp__playwright-2 mcp__playwright-3 PushNotification" --output-format json
Symptom: Every tool call (Bash/PowerShell/Write/Edit) is denied as "requires approval," even though those tool names are explicitly included in --allowedTools and also covered by project-level permissions/autoMode allow rules. The session exits in ~4 seconds with $0 cost, 0 tokens. Since this runs fully unattended (no one present to approve), nothing is ever accomplished.
Timeline:
- Last successful run: 2026-09-27 22:00 (completed normally, real cost ~$40).
- First failed run: 2026-09-28 22:00, and every scheduled run since (one nightly run + hourly daytime runs) through at least 2026-10-06 — confirmed across ~15 consecutive hourly runs on a single day (2026-10-05), all showing the identical symptom.
- No Claude Code version change occurred in that window (last CLI update was 2026-09-22, six days before the first failure).
- No change to the relevant local permission settings (
.claude/settings.local.jsonautoMode allow rules) at the failure boundary — verified via git history of the config files.
Reproduction: Manually running the exact same command, same --allowedTools string, same environment variables, from an interactive session (and also via a subagent process with equivalent env vars) — zero permission denials, every time, across multiple independent attempts over several days.
Ruled out as correlating factors: Windows session lock state (reproduced the failure moments ago with the screen unlocked and the user actively present), time of day, and battery vs. AC power state — none of these correlate with the failure.
Not in use: --permission-prompts none is not currently passed.
Question: Is there a known difference in how --allowedTools permission evaluation behaves when the process tree originates from the Windows Task Scheduler service vs. an interactive shell (e.g. window station/session isolation, access token type, environment variable inheritance) that could cause already-allowlisted tools to be denied? This is blocking all unattended/scheduled automation on this machine since 2026-09-28.
3 Comments
Zero tokens and a four second exit means no API call was made, so the tools weren't denied mid-session; the run died before the first turn. In our experience that's a quoting problem, not a window station one. We'd write the resolved command line to a file from inside the ps1, right before calling claude, and diff it against what you type manually. The
--append-system-promptwith no visible value stands out: if that's a variable that comes up empty under the scheduler (user env vars don't always load for scheduled tasks), the flag eats--allowedToolsas its text and the tool list becomes a bare positional argument. Also log(Get-Command claude).Sourceandclaude --versionfrom the task itself, since a scheduled PATH can resolve a different install than your shell does, and that would explain the 09-28 boundary without a visible update.Thanks for taking a look — really appreciate the debugging ideas.
We checked each of the three hypotheses against our actual scripts, and none of them held up on our side:
--append-system-promptresolving empty: the value we pass is a literal string hardcoded in the script, not sourced from an environment variable, so it can't come up empty under the scheduler.claudeinstall under the scheduler: all our launcher scripts call an absolute path (%APPDATA%\npm\claude.cmd) rather than relying on PATH lookup. We also checked the Scheduled Task's principal — it runs as the same interactive user, not SYSTEM or a different account.Also, the "0 tokens / 4-second exit" signature doesn't quite match what we're seeing now: today's two failing autonomous-runner tasks ran 238s and 641s respectively before returning an error decision, still with 0 tokens recorded. That still points to something blocking tool calls locally before (or without) ever reaching the model, rather than an argument-parsing crash.
Happy to share more raw logs if useful — still stuck on root cause from our side.
Update after further investigation (2026-10-12). We split one headless task's processing into two separate
claude -psessions — a long "investigate" session that does research/planning but performs zero Write/Edit/script-execution, and a short "apply" session (fresh process, minimal prompt, started right after) whose only job is to carry out the writes the investigate session decided on. This reduced — but did not eliminate — the denial rate, which pointed us toward a few more specific observations that might help narrow this down:Edittool call with "requires approval"/permission-denied style messages. On a different task the day before, an equivalent short "apply"-style session succeeded on the first attempt. Same machine, same script, same--allowedTools/--permission-mode auto --permission-prompts none.-p, no Task Scheduler), editing one of our own hook scripts under.claude/hooks/was denied twice in a row by "the Claude Code auto mode classifier," then succeeded on the third attempt with byte-for-byte identical tool input each time..claude/hooks/*.shdenial looks like the same over-broad classification described in #100791 — our hook scripts are ordinary user-authored project files (not Claude Code's own binary/settings/config), but edits to them get treated as self-modification-flagged.Given this looks like the same family of issue as #87809 / #97911 (auto-mode classifier denial with no escalation path in unattended runs) rather than something specific to our Task Scheduler setup, we've stopped trying to find a local fix and are instead building a workaround: retry the write-only step a few times as fresh processes (since retries do sometimes succeed), and if it still fails after that, fall back to flagging the task for a human to finish in an interactive session instead of silently losing the work. Documenting here in case the "fresh-process retry sometimes helps, sometimes doesn't" data point is useful alongside the other reports.