在后台运行 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
| 操作系统 | 工具 | 定义位置 | 日志 |
|---|---|---|---|
| macOS | launchd (LaunchAgent) | ~/Library/LaunchAgents/<label>.plist | StandardOutPath/StandardErrorPath |
| Linux | cron | crontab -e 或 /etc/cron.d/<name> | MTA 或重定向 >> log.txt 2>&1 |
| Windows | 任务计划程序 | GUI 或 PowerShell Register-ScheduledTask | 事件查看器或文件重定向 |
五个常见陷阱:
- PATH 为空 — 命令找不到
- API 密钥以明文存入 plist/crontab(禁止这样做)
- 同一任务并发运行产生竞争
- 静默失败 — 没有失败通知
- 机器休眠 — 任务被跳过
§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.plist1.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| 字段 | 值 | 含义 |
|---|---|---|
0 | 0 | 整点 |
9 | 9 | 上午 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>&12.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
EOFappuser 字段指定运行用户,这对安全性很重要。
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.targetsudo 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 + R → taskschd.msc → 任务计划程序库 → "DailyPRReview"。
3.3 立即测试
Start-ScheduledTask -TaskName "DailyPRReview"
Get-ScheduledTaskInfo -TaskName "DailyPRReview" | Select LastRunTime, LastTaskResultLastTaskResult: 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。
后续步骤
- Claude Code 配置 — 前置条件
- Claude Code hooks — 自动化事件模式
- 多代理工作流 — 多代理协作
参考资料
更新日志
- 2026-05-12 — 初稿(devAlice M2 种子扩展)