跳到正文
跟我做
自媒体自动化11 分钟Node.js

脚本昨晚没跑:定时任务的四个静默失败

手动能跑,定时就是不动。先分清没触发还是执行失败,再按 PATH、工作目录、休眠、日志四层排查,每条都给确认命令和修法。

约 11 分钟
本页目录
  1. 这篇教程解决什么问题
  2. 先分清:没触发,还是触发了但失败
  3. 第一步:让任务留下证据
  4. 失败一:PATH 找不到命令
  5. 失败二:工作目录不对
  6. 失败三:机器休眠,任务根本没执行
  7. 失败四:跑了,但没有日志
  8. 症状对号表
  9. 上线前复核
  10. 下一步读什么

30 秒速查

  • 先看日志有没有新内容:整段空白,多半是没触发;有报错,是脚本环境不对。
  • 在调度器里跑一次 heartbeat.mjs,拿到工作目录、Node 路径和 PATH。
  • 按顺序对号:PATH(找程序的名单) → 工作目录 → 休眠 → 日志。

这篇教程解决什么问题

脚本在终端里跑得好。设了每天 9 点,第二天打开电脑,什么都没发生——没弹窗,没报错,也没日志。

定时任务(专业说法:调度器,常见 cron、launchd、Windows 任务计划)失败时大多不出声。你以为它跑了,其实没跑;或者它跑了,但脚本一启动就死在环境上。

四个失败长得一样:该有结果时没有结果。下面先分清有没有触发,再逐层排查。

先分清:没触发,还是触发了但失败

先看日志(下文以 cron.log 为例)。

ls -l cron.log
文件列表里 cron.log 显示 0 字节,下面是「整个文件是空的:一次都没跑过」
① 0 字节:一次都没跑过
  • 时间戳在昨晚 9 点之后:触发了,问题在脚本环境,查失败一、二、四。
  • 没有文件,或时间戳停在几天前:没触发,问题在调度器或机器,查失败一、三,以及任务有没有加载。
  • 文件在,但昨晚没有新行:可能跑了没输出,归到失败四。

别把「没触发」当「脚本报错」修:第一条命令完全不同。

  1. 看日志时间戳

    有昨晚的新行=触发了;没有=没触发。

  2. 触发了 → 查脚本环境

    PATH、工作目录、业务报错,对应失败一、二、四。

  3. 没触发 → 查调度器或机器

    Node 没起来、任务没加载、机器在休眠,对应失败一、三。

图 1:先看日志时间戳,把问题分成两半。

第一步:让任务留下证据

先让任务自己说话。保存为 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
短 PATH 下跑 node heartbeat.mjs,输出 /bin/sh: 1: node: not found 和 exit=127
① 找不到 node ② 退出码 127(短 PATH 下真跑)
  • 怎么发现:crontab -l 里写的是 node,日志文件根本没生成。
  • 怎么确认:上面命令返回 127,意思是「命令没找到」。
  • 怎么修:解释器写绝对路径,crontab 顶部加一行 PATH:
PATH=/usr/local/bin:/usr/bin:/bin
0 9 * * * cd "/项目目录" && "/绝对路径/node" "task.mjs" >> "cron.log" 2>&1
终端 crontab -l 输出两行:PATH 一行,下面一条 0 9 * * * 的任务,重定向到 cron.log
① 时间表达式(文章示例;本机 crontab 受权限限制读不到)

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';Windows powercfg /sleepstudy;Linux journalctl | 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」比退出码有用;数字不对就是业务失败。

cat cron.log 输出一行 JSON,开头是真实的 at 时间戳,cwd 是 ~/project
① 真实时间戳 ② 跑过了(真跑 heartbeat 写进日志)

症状对号表

症状 最可能的原因 第一条命令
完全没有日志 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说「退订」就删掉。详见隐私政策。

同方向继续

全部教程