脚本昨晚没跑:定时任务的四个静默失败
手动能跑,定时就是不动。先分清没触发还是执行失败,再按 PATH、工作目录、休眠、日志四层排查,每条都给确认命令和修法。
本页目录
30 秒速查
- 先看日志有没有新内容:整段空白,多半是没触发;有报错,是脚本环境不对。
- 在调度器里跑一次
heartbeat.mjs,拿到工作目录、Node 路径和 PATH。- 按顺序对号:PATH(找程序的名单) → 工作目录 → 休眠 → 日志。
这篇教程解决什么问题
脚本在终端里跑得好。设了每天 9 点,第二天打开电脑,什么都没发生——没弹窗,没报错,也没日志。
定时任务(专业说法:调度器,常见 cron、launchd、Windows 任务计划)失败时大多不出声。你以为它跑了,其实没跑;或者它跑了,但脚本一启动就死在环境上。
四个失败长得一样:该有结果时没有结果。下面先分清有没有触发,再逐层排查。
先分清:没触发,还是触发了但失败
先看日志(下文以 cron.log 为例)。
ls -l cron.log

- 时间戳在昨晚 9 点之后:触发了,问题在脚本环境,查失败一、二、四。
- 没有文件,或时间戳停在几天前:没触发,问题在调度器或机器,查失败一、三,以及任务有没有加载。
- 文件在,但昨晚没有新行:可能跑了没输出,归到失败四。
别把「没触发」当「脚本报错」修:第一条命令完全不同。
看日志时间戳
有昨晚的新行=触发了;没有=没触发。
触发了 → 查脚本环境
PATH、工作目录、业务报错,对应失败一、二、四。
没触发 → 查调度器或机器
Node 没起来、任务没加载、机器在休眠,对应失败一、三。
第一步:让任务留下证据
先让任务自己说话。保存为 resources/cron-configs/heartbeat.mjs:
console.log(JSON.stringify({
at: new Date().toISOString(),
cwd: process.cwd(),
node: process.execPath,
path: process.env.PATH,
argv: process.argv.slice(2),
}));
它打印时间、目录、Node、PATH 和参数,四个失败里三个会现形。
Linux 和 macOS 先用 env -i 模拟最小环境:
env -i HOME="$HOME" PATH=/usr/bin:/bin \
/绝对路径/node /绝对路径/heartbeat.mjs
本机真实输出(路径里的用户名已替换成 you,其余逐字照原样):
{"at":"2026-10-26T14:02:57.604Z","cwd":"/","node":"/home/you/.nvm/versions/node/v24.22.1/bin/node","path":"/usr/bin:/bin","argv":[]}
cwd 是 /,path 只有 /usr/bin:/bin——失败一和失败二的种子都在这里。
Windows 任务计划不过 shell:填 Node 和脚本的完整路径。
失败一:PATH 找不到命令
定时任务找程序的那张名单,比你在终端里短。PATH 就是那张名单,电脑按它一个个目录去找程序。.zshrc、.bashrc 是你开终端时自动读的配置文件,它不读;nvm 装的 Node 只记在这种文件里,所以它找不到。
第一层:连 Node 都没起来。 症状是完全没有日志。用调度器的 PATH 跑一次:
env -i HOME="$HOME" PATH=/usr/bin:/bin /bin/sh -c 'node heartbeat.mjs; echo "exit=$?"'
本机真实输出:
/bin/sh: 1: node: not found
exit=127

- 怎么发现:
crontab -l里写的是node,日志文件根本没生成。 - 怎么确认:上面命令返回 127,意思是「命令没找到」。
- 怎么修:解释器写绝对路径,crontab 顶部加一行 PATH:
PATH=/usr/local/bin:/usr/bin:/bin
0 9 * * * cd "/项目目录" && "/绝对路径/node" "task.mjs" >> "cron.log" 2>&1

launchd 加环境变量:
<key>EnvironmentVariables</key>
<dict><key>PATH</key><string>/usr/local/bin:/usr/bin:/bin</string></dict>
Windows 在「操作」里写 C:\Program Files\nodejs\node.exe 的完整路径。
第二层:脚本里的命令找不到。 日志有内容,但报 spawn ffmpeg ENOENT——内部调用用的还是短 PATH。修法同上:顶部设 PATH,或用绝对路径。
失败二:工作目录不对
终端里 ./data/report.csv 找得到;定时任务默认在 $HOME(cron)或 /(launchd 未设 WorkingDirectory)。相对路径换了地方,文件明明在,脚本说找不到。
- 怎么发现:日志里是相对路径的
ENOENT。 - 怎么确认:heartbeat 的
cwd不是项目目录,或cd / && /绝对路径/node /绝对路径/task.mjs能复现。 - 怎么修:在调度器里指定目录,或让代码不依赖当前目录。
crontab 行首加 cd:
0 9 * * * cd "/项目目录" && "/绝对路径/node" "task.mjs" >> "cron.log" 2>&1
launchd 加 WorkingDirectory,Windows 填「起始于」。
更稳的是改代码:
import path from "node:path";
import { fileURLToPath } from "node:url";
const here = path.dirname(fileURLToPath(import.meta.url));
const dataFile = path.join(here, "data", "report.csv");
import.meta.url 是当前文件的位置,从哪里启动都找得到。
失败三:机器休眠,任务根本没执行
笔记本合盖、台式机睡眠,调度器不会在梦里叫醒自己。醒来以后,昨晚 9 点已经过去了。
- 怎么发现:日志在预期时间整段空白,前后正常;不是报错,是没记录。
- 怎么确认:对照系统睡眠日志——macOS
pmset -g log | grep -E 'Sleep|Wake';Windowspowercfg /sleepstudy;Linuxjournalctl | grep -i suspend。
修法:
| 系统 | 做法 | 会不会补跑 |
|---|---|---|
| Windows | 「条件」勾「唤醒计算机」;「设置」勾「错过计划后尽快启动」 | 开机且条件满足时补一次;关机期间不保证 |
| Linux cron | 没有补跑机制 | 不补 |
| Linux systemd timer | 单元里加 Persistent=true |
下次开机补一次 |
| macOS launchd | 睡眠期间错过的 StartCalendarInterval 任务,唤醒后是否补跑待实测 |
不确定,不要依赖 |
macOS 自测:任务设到 2 分钟后 → 合盖 → 等 5 分钟 → 开盖 → 看日志。各版本可能不同。
最稳的是换机器:放常开机器或服务器。
失败四:跑了,但没有日志
情况一:没重定向。 cron 默认发邮件,没邮件服务就丢;launchd 不给 StandardOutPath 也丢;Windows 不捕获控制台输出。
crontab 行尾加 >> "cron.log" 2>&1;launchd 加两个键:
<key>StandardOutPath</key><string>/绝对路径/cron.log</string>
<key>StandardErrorPath</key><string>/绝对路径/cron.log</string>
Windows 用 cmd /c 包一层:
程序:C:\Windows\System32\cmd.exe
参数:/c "C:\Program Files\nodejs\node.exe" "C:\task\task.mjs" >> "C:\task\cron.log" 2>&1
[视频待办] 在 Windows 任务计划程序里查看一次任务的「上次运行结果」和「历史记录」,约 30 秒。必须拍到「常规 / 操作 / 条件 / 历史记录」四个标签页,以及「上次运行结果」那一列的值。
情况二:目录不可写。 确认任务以哪个用户运行,再看它对日志目录的写权限。
情况三:进程成功,业务失败。 退出码 0 只代表 Node 没崩。网络失败被吞、返回空数据、文件写了一半,都可能是 0。
退出码:macOS launchctl list、Windows「上次运行结果」、systemd systemctl --user status;cron 只能看日志。
脚本首尾各打一行:
console.log(`[${new Date().toISOString()}] start`);
// ... 任务 ...
console.log(`[${new Date().toISOString()}] done: 37 items`);
「done: 37 items」比退出码有用;数字不对就是业务失败。

症状对号表
| 症状 | 最可能的原因 | 第一条命令 |
|---|---|---|
| 完全没有日志 | Node 没起来(PATH),或任务没加载 | env -i PATH=/usr/bin:/bin sh -c 'node -v; echo $?' |
日志报 command not found / ENOENT(命令) |
脚本内部调用的命令不在 PATH | 在 heartbeat 里打印 process.env.PATH |
日志报 ENOENT(文件) |
工作目录不对 | 打印 process.cwd(),或从 / 跑一次 |
| 某个时间点整段空白 | 机器休眠,或任务没触发 | pmset -g log / powercfg /sleepstudy / journalctl |
| 退出码非 0 | 脚本本身报错 | 看日志最后 20 行 |
上线前复核
- 解释器写的是绝对路径,不是
node - 工作目录固定,或代码用
import.meta.url定位文件 - 机器在任务时间点醒着,或已配置补跑
- 输出重定向到日志,日志目录可写
- 日志首尾有时间戳和处理条数
五条全过,再等一个周期。别在任务该跑的当晚改配置。
下一步读什么
定时解决「按时启动」;断点续跑、去重和失败重试是下一层:
批量任务跑到一半断了:用 SQLite 做去重、断点续跑和失败重试
脚本能自己跑了,下一步不是再加一个脚本,是把它做出来的东西放到网上去:从 GitHub 到 Cloudflare:把静态站真正上线——那篇讲的是域名、部署和最容易被误判的「部署成功、线上还是旧的」。
也属于自媒体自动化入门的「让它自己跑」环节。
PATH、cwd 和退出码本机实测;macOS、Windows 命令未实测,睡眠补跑待实测。
常见问题
手动能跑,为什么定时就不行?
定时任务不在你的终端里跑:PATH(电脑找程序时翻的名单)更短,工作目录通常不是项目目录,也不读 .zshrc(开终端时自动读的配置文件)。脚本没变,环境变了。先让它打印 cwd 和 PATH。
关机或休眠期间错过的任务,会补跑吗?
多数不会。Linux cron 不补;systemd timer 加 Persistent=true 下次开机补一次;Windows 可勾「错过计划后尽快启动」,但不保证关机期间执行;macOS 各版本不一致,本文给自测法。
日志在哪里看?
重定向到哪就在哪;没设就没有。crontab 看行尾的 >>,launchd 看 StandardOutPath,Windows 看有没有用 cmd /c 包重定向。
退出码是 0,为什么结果还是不对?
退出码 0 只说明进程没崩。接口失败被吞、返回空数据、文件写了一半,都可能是 0。日志要打印处理条数,不能只看退出码。
每周一张复习表
每周分享一张实用复习表,不收钱不打扰,想走随时走
邮箱存在 Cloudflare KV 里,只用来发每周更新;任何时候写信到hello@genwozuo.com说「退订」就删掉。详见隐私政策。