Updated: · Automation Notes admin

Run Claude Code Automatically Every Day with Windows Task Scheduler

"Organize my Notion tasks every morning." "Summarize today's notes every evening." Once you have recurring tasks for Claude Code, launching it manually each time becomes tedious. On Windows, you can run it on a set schedule using the built-in Task Scheduler.

However, it often happens that while it works when run manually, it fails the moment you automate it. In this article, I'll share how I set up my daily automated runs and the pitfalls I encountered.

How the Automation Works

  1. Task SchedulerLaunches PowerShell at the scheduled time
  2. PowerShell ScriptChanges to the working folder and calls claude -p
  3. Claude CodePerforms the requested task and returns the result
  4. Logs & Exit CodesSaves results to a file; marks task as failed on error
Instead of registering Claude Code directly in the task, a PowerShell script sits in between. This makes it easier to save logs and flag failures.

Run Once Non-interactively with claude -p

By adding -p (--print), Claude Code runs a single task without opening the interactive interface and then exits. The official documentation introduces this as a way to use it from scripts or CI.

claude -p "Organize today's tasks" --output-format json

Here are three key points to keep in mind:

Note that while adding --bare makes startup faster, it does not load CLAUDE.md or MCP configurations, and it requires an API key instead of your regular login session. If you want to use your everyday settings and the Notion MCP as-is, run it without this flag.

Limit Permissions to "Only What Is Allowed"

During automated runs, no one is there to respond when a prompt asks for confirmation. Therefore, specify the tools allowed to run with --allowedTools, and set the mode to reject anything else without prompting via --permission-mode dontAsk. According to the official documentation, in dontAsk mode "Claude Code denies every call that would otherwise prompt," while actions covered by your --allowedTools entries still run.

claude -p "/daily-tasks" `
  --allowedTools "Read,Write,mcp__notion" `
  --permission-mode dontAsk `
  --output-format json

mcp__notion is the syntax to allow all tools from an MCP server registered under the name notion. You can verify the server name in Claude Code's /mcp list. Rather than allowing everything, listing only what is required for that specific task prevents unintended operations.

Prepare a PowerShell Script

Putting a script in between makes it much easier to manage than calling claude directly from the task. Below is a simplified version of what I use.

run_daily.ps1 (Save as UTF-8 with BOM)

# Called every morning by Task Scheduler
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

$ProjectDir = "C:\path\to\working_folder"
$LogDir = Join-Path $ProjectDir "logs"
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
Set-Location $ProjectDir

# Specify the full path found via 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"

# Without this, the task will appear successful even if claude fails
exit $code

Here are the key points to keep in mind for this script:

Save Scripts Containing Non-ASCII Characters as UTF-8 with BOM

Windows PowerShell 5.1, which comes pre-installed with Windows, reads UTF-8 scripts without a BOM using the legacy "ANSI" encoding. Microsoft's documentation says: "If you need to use non-Ascii characters in your scripts, save them as UTF-8 with BOM." That includes accented letters, symbols, emoji, and non-Latin scripts. If you save a script containing non-ASCII characters in comments or strings without a BOM, it can cause errors during parsing. When saving in Notepad or VS Code, be sure to select "UTF-8 with BOM".

Registering the Task

While you can register the task through the GUI, writing it in PowerShell makes it easier to review and adjust your settings later.

$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
SettingMeaningReason
-ExecutionPolicy BypassBypasses execution policy restrictions specifically for running this scriptBecause the default Windows execution policy (Restricted) prevents running scripts
-StartWhenAvailableRuns the task later even if the scheduled time was missedTo run it upon waking up on days when the PC was asleep at the scheduled time
-AllowStartIfOnBatteriesStarts the task even when running on battery powerBecause by default, tasks are configured not to start while on battery power
-DontStopIfGoingOnBatteriesDoes not stop if disconnected from AC power mid-runTo prevent leaving tasks in a half-finished state if stopped midway
-MultipleInstances IgnoreNewDoes not start a new instance if the previous one is still runningTo prevent overlapping runs from writing duplicate data
-ExecutionTimeLimitUpper limit on execution timeDefaults to 3 days if unspecified; keeping it short prevents tasks from hanging indefinitely

Laptops Often Fail to Run Due to Battery Settings

According to Microsoft's documentation, the setting that keeps a task from starting on battery power (DisallowStartIfOnBatteries) is on by default. Since I carry and use my laptop on the go, leaving this setting enabled caused the task to remain queued and never run on days when it wasn't plugged in. If a task runs manually but fails when automated, check this setting first. In the GUI, it is the "Start the task only if the computer is on AC power" option on the Conditions tab of the task's properties.

Managing Costs and Skipping Unneeded Days

Claude Code consumes usage allowance (or incurs costs) with every run. In my environment, for tasks like reading and writing to Notion, total_cost_usd in the JSON was generally around $0.50 to $1.00 per run. Running it every day, even on days with nothing to do, adds up to a noticeable amount.

Therefore, before invoking Claude Code, I use a small PowerShell or Python check to verify if there is any work to do today, exiting early if there isn't. For example, exiting if there are no new requests in Notion, or exiting if there are no files to import. Handling things that can be decided without AI beforehand is the key to saving costs.

Summary

If you also automate Python scripts alongside this, you need to pay attention to where you place the virtual environment (venv). In A Python venv Inside OneDrive Can Break Your Scheduled Scripts, I share an issue I ran into firsthand. To notice when a scheduled task has quietly stopped, see Catch Task Scheduler Failures: Check Your Automated Runs Every Morning.