devAlice
← AI Agents

在后台运行 AI 代理 — cron · launchd · 任务计划程序

定时运行 AI 代理(如 Claude Code)的调度方案,无需人工守候键盘。对比 macOS launchd · Linux cron · Windows 任务计划程序,并列举 AI 代理专属陷阱。

Claude Code 等 AI 代理并不局限于守在键盘前使用。夜间 PR 审查Issue 分拣README 同步日志摘要 — 无人值守运行时,价值会成倍累积。

我认为调度运行是 AI 代理发挥最大价值的场景之一。以前我们只把 AI 当作同步工具;如今通过自动调度,它能在操作者休息时持续推进工作,因为那才是真正的异步增益。

本指南针对上述场景,对比三大操作系统的标准调度工具(macOS launchd、Linux cron、Windows 任务计划程序),并重点介绍 AI 代理特有的常见陷阱(API 密钥安全、日志捕获、失败通知)。

TL;DR

操作系统工具定义位置日志
macOSlaunchd (LaunchAgent)~/Library/LaunchAgents/<label>.plistStandardOutPath/StandardErrorPath
Linuxcroncrontab -e/etc/cron.d/<name>MTA 或重定向 >> log.txt 2>&1
Windows任务计划程序GUI 或 PowerShell Register-ScheduledTask事件查看器或文件重定向

五个常见陷阱

  1. PATH 为空 — 命令找不到
  2. API 密钥以明文存入 plist/crontab(禁止这样做)
  3. 同一任务并发运行产生竞争
  4. 静默失败 — 没有失败通知
  5. 机器休眠 — 任务被跳过

§1–§3 覆盖各 OS 专属模式;§4 介绍陷阱。

前提条件

  • 一个需要运行的 AI 代理(如 Claude Code、Cursor CLI、自定义脚本)
  • 通过环境变量或密钥管理器管理的 API 密钥
  • 一个 shell 脚本(如 ~/agents/daily-pr-review.sh

1. macOS — launchd LaunchAgent — 10 分钟

macOS 也有 cron,但 Apple 推荐使用 launchd。机器休眠或重新登录后的补跑也更可靠。

1.1 编写 plist

~/Library/LaunchAgents/dev.local.daily-pr-review.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>dev.local.daily-pr-review</string>
 
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>-lc</string>
    <string>$HOME/agents/daily-pr-review.sh</string>
  </array>
 
  <!-- 环境变量:PATH 可能为空,需显式设置 -->
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key>
    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
    <!-- 不要在此处放 ANTHROPIC_API_KEY — 见 §4.2 -->
  </dict>
 
  <!-- 每天 09:00 -->
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key><integer>9</integer>
    <key>Minute</key><integer>0</integer>
  </dict>
 
  <!-- 休眠后补跑 -->
  <key>RunAtLoad</key><false/>
  <key>StartOnMount</key><false/>
 
  <!-- 日志 -->
  <key>StandardOutPath</key>
  <string>/tmp/daily-pr-review.out.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/daily-pr-review.err.log</string>
</dict>
</plist>

1.2 注册 + 运行

# 注册
launchctl load ~/Library/LaunchAgents/dev.local.daily-pr-review.plist
 
# 立即运行(跳过计划)
launchctl start dev.local.daily-pr-review
 
# 查看状态
launchctl list | grep daily-pr-review
 
# 取消注册
launchctl unload ~/Library/LaunchAgents/dev.local.daily-pr-review.plist

1.3 休眠后补跑

cron 在休眠期间会跳过任务。launchd 会在唤醒时运行一次(注意:StartCalendarInterval 只补跑一次)。

对于每小时等周期的补跑:

<key>StartInterval</key>
<integer>3600</integer>  <!-- 每小时 -->

→ 唤醒后运行一次,不同于 cron。


2. Linux — cron — 5 分钟

2.1 编辑 crontab

crontab -e

示例:

# m h dom mon dow  command
0 9 * * *  /home/user/agents/daily-pr-review.sh >> /home/user/logs/pr-review.log 2>&1
字段含义
00整点
99上午 9 点
* * *每天每天

每天 09:00,将 stdout/stderr 追加到日志文件。

2.2 显式设置 PATH

cron 的 PATH 非常精简。在脚本中或在 crontab 顶部设置它:

PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/sbin
SHELL=/bin/bash
 
0 9 * * *  $HOME/agents/daily-pr-review.sh >> $HOME/logs/pr-review.log 2>&1

2.3 系统级 cron(/etc/cron.d/

服务器上的 root 级注册:

sudo tee /etc/cron.d/daily-pr-review > /dev/null <<'EOF'
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
0 9 * * * appuser /home/appuser/agents/daily-pr-review.sh >> /var/log/agent.log 2>&1
EOF

appuser 字段指定运行用户,这对安全性很重要。

2.4 systemd Timer(现代替代方案)

cron 的替代方案。Unit 文件统一了重启、故障处理和日志记录。

# /etc/systemd/system/pr-review.service
[Unit]
Description=Daily PR review agent
 
[Service]
Type=oneshot
User=appuser
ExecStart=/home/appuser/agents/daily-pr-review.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/pr-review.timer
[Unit]
Description=Run daily PR review at 09:00
 
[Timer]
OnCalendar=*-*-* 09:00:00
Persistent=true   # 宕机期间错过时,启动后补跑
 
[Install]
WantedBy=timers.target
sudo systemctl enable --now pr-review.timer
journalctl -u pr-review.service    # 查看日志

Persistent=true 是相对于 cron 的最大优势 — 服务器宕机后自动补跑。


3. Windows — 任务计划程序 — 10 分钟

GUI 可用,但使用 PowerShell 更易于复现和自动化。

3.1 通过 PowerShell 注册

# 操作
$action = New-ScheduledTaskAction `
  -Execute "wsl.exe" `
  -Argument "-d Ubuntu -e bash -lc '~/agents/daily-pr-review.sh'"
 
# 触发器:每天 09:00
$trigger = New-ScheduledTaskTrigger -Daily -At 9am
 
# 设置:用户未登录时也运行;从休眠唤醒后运行
$settings = New-ScheduledTaskSettingsSet `
  -StartWhenAvailable `
  -AllowStartIfOnBatteries `
  -DontStopIfGoingOnBatteries `
  -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
  -RestartCount 2 `
  -RestartInterval (New-TimeSpan -Minutes 10)
 
# 注册
Register-ScheduledTask `
  -TaskName "DailyPRReview" `
  -Action $action `
  -Trigger $trigger `
  -Settings $settings `
  -Description "Run Claude Code daily PR review in WSL"

3.2 在 GUI 中确认

Win + Rtaskschd.msc → 任务计划程序库 → "DailyPRReview"。

3.3 立即测试

Start-ScheduledTask -TaskName "DailyPRReview"
Get-ScheduledTaskInfo -TaskName "DailyPRReview" | Select LastRunTime, LastTaskResult

LastTaskResult: 0 表示成功。

3.4 调用 WSL

如上所示,wsl.exe -d Ubuntu -e bash -lc '...'。环境变量和 API 密钥存放在 WSL 的 ~/.bashrc 或独立的 env 文件中。

3.5 日志捕获

任务计划程序自带的日志较为稀疏。在脚本内部重定向:

# ~/agents/daily-pr-review.sh
#!/usr/bin/env bash
set -euo pipefail
LOG="$HOME/logs/pr-review-$(date +%Y%m%d).log"
mkdir -p "$(dirname "$LOG")"
exec >> "$LOG" 2>&1
echo "[$(date)] starting..."
# 运行代理
claude-code --resume --task pr-review --quiet
echo "[$(date)] done."

4. AI 代理专属陷阱

4.1 PATH 为空 — 「跑不起来」的第一大原因

交互式 shell 的 PATH ≠ launchd/cron 的 PATH。在脚本顶部声明它:

#!/usr/bin/env bash
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH"

或通过 plist/crontab 的 EnvironmentVariables / PATH= 头部设置。

验证:

launchctl getenv PATH         # macOS
sudo -u appuser env | grep PATH  # Linux cron 用户

4.2 切勿将 API 密钥放入 plist/crontab

若在 plist 的 EnvironmentVariables 块中写入 ANTHROPIC_API_KEY=sk-ant-...,会有以下风险:

  • ls -la 即可看到文件
  • 密钥以明文存入 Time Machine / iCloud 备份
  • 同机其他用户可通过 cat ~/Library/LaunchAgents/*.plist 读取

替代方案

  • macOS:Keychain + 脚本通过 security find-generic-password -s anthropic-api -w 读取
  • Linux:单独的 ~/.config/agent/env 文件,chmod 600 + 脚本中 source
  • Windows:Get-Credential 或凭据管理器

脚本示例(macOS):

#!/usr/bin/env bash
set -euo pipefail
export ANTHROPIC_API_KEY="$(security find-generic-password -s anthropic-api -w 2>/dev/null)"
[ -z "$ANTHROPIC_API_KEY" ] && { echo "missing API key in Keychain"; exit 1; }
# ... 运行代理

一次性注册密钥:

security add-generic-password -s anthropic-api -a "$USER" -w 'sk-ant-...'

4.3 防止并发运行 — flock / 单实例

重复运行 = 双倍 API 费用 + 竞态条件。用 flock 加锁:

#!/usr/bin/env bash
exec 200>/tmp/daily-pr-review.lock
flock -n 200 || { echo "already running"; exit 0; }
# ... 运行代理

flock -n = 非阻塞;如已上锁则立即退出。

4.4 不要静默失败 — 失败通知

默认情况下,stderr 只是写入日志文件,不会主动通知。使用 trap 发送通知:

#!/usr/bin/env bash
set -euo pipefail
 
notify_failure() {
  local code=$?
  if [ $code -ne 0 ]; then
    # macOS — terminal-notifier 或 osascript
    osascript -e "display notification \"PR review failed (exit $code)\" with title \"Agent\""
    # 或 Slack webhook / email / Sentry
    # curl -X POST -H 'Content-Type: application/json' \
    #   -d "{\"text\":\"Agent failed: exit $code\"}" "$SLACK_WEBHOOK_URL"
  fi
}
trap notify_failure EXIT
 
# 运行代理

4.5 休眠 / 启动后补跑

  • macOS launchd:StartCalendarInterval 在休眠唤醒后补跑一次
  • Linux systemd timer:使用 Persistent=true
  • Windows:任务计划程序的**「错过计划时间后尽快启动任务」**选项,设为开启

唯独 cron 缺乏休眠后补跑能力 — 这正是笔记本电脑上鲜有人用 cron 的原因。


5. 真实场景 — Claude Code 每日 PR 审查

~/agents/daily-pr-review.sh

#!/usr/bin/env bash
set -euo pipefail
 
# 1. PATH + 密钥
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
export ANTHROPIC_API_KEY="$(security find-generic-password -s anthropic-api -w)"
export GITHUB_TOKEN="$(security find-generic-password -s github-token -w)"
 
# 2. 加锁
exec 200>/tmp/daily-pr-review.lock
flock -n 200 || exit 0
 
# 3. 日志
LOG="$HOME/logs/pr-review-$(date +%Y%m%d).log"
mkdir -p "$(dirname "$LOG")"
exec >> "$LOG" 2>&1
echo "[$(date)] starting daily PR review..."
 
# 4. 工作
cd "$HOME/code/my-repo"
git fetch --quiet origin
gh pr list --state open --json number,title,author --jq '.[] | select(.author.login != "me")' | \
  while read -r pr; do
    num=$(echo "$pr" | jq -r .number)
    echo "Reviewing PR #$num..."
    claude-code --task "Review PR #$num — focus on security, performance, missing tests" --quiet
  done
 
echo "[$(date)] done."

将脚本注册为 LaunchAgent,每天 09:00 自动触发。


6. 故障排查

launchd "Service exited with abnormal code: 127"

PATH 为空或脚本无执行权限。运行 chmod +x ~/agents/*.sh,并在 plist 中显式设置 PATH。

cron 从不运行且没有日志

  • crontab 语法错误:通过 crontab -l 检查
  • 没有 MTA(mailutils)时,stderr 会丢失 → 显式 2>&1 >> log
  • 使用 journalctl -u cron(或 cronie)检查运行尝试

任务计划程序"Last Task Result: 0x1"

脚本退出码为 1。在 WSL 中直接运行脚本进行调试,检查 $LASTEXITCODE

claude-code 命令找不到

脚本 PATH 中缺少 ~/.npm-global/bin 或你的 npm 全局 bin。将 which claude-code 所在目录添加到 PATH。


后续步骤

参考资料

更新日志

  • 2026-05-12 — 初稿(devAlice M2 种子扩展)