1. 为什么我要在 Claude Code 里折腾 Cron 定时任务Claude Code 的 Cron 定时任务简单说就是给 AI 编码助手装了一个闹钟到点了它自己醒来按你事先写好的 prompt 去干活干完把结果丢回给你。它适合谁适合那些每天要重复做同一件检查、同一份汇总、同一次轮询的开发者——比如每隔十分钟看一次部署状态、每天早上汇总昨天的 git 提交、每周一生成一份依赖升级清单。这些事本身不难难的是你总得记得去做而人恰恰最容易忘。我之前的做法很土开一个终端挂着watch或者写个 shell 脚本塞进系统 crontab。问题是脚本只能执行写死的命令遇到接口返回 500 了帮我看看日志里有没有线索这种需要理解上下文的任务脚本就傻了。Claude Code 的 Cron 把执行者从 shell 换成了 AI Agent你描述目标它自己决定调哪些工具、怎么处理异常。这篇就按从零到能跑的顺序把配置骨架、TaoToken 通道接入、创建触发验证、以及我踩过的坑一次讲清楚。需要先明确一个边界Claude Code 的 Cron 不是操作系统级的 cron 守护进程它只在 Claude Code 运行期间生效。你可以把它理解成会话内的调度器Claude Code 一关任务就不触发了。想让任务在重启后还在得靠durable: true把它写进磁盘。这个前提决定了它适合开发期间的自动化而不是服务器上的常驻运维。2. 前置准备用 TaoToken 统一 Key 和 API 通道在写任何定时任务之前先把模型通道理顺。Claude Code 这类工具默认走官方端点但很多人在国内网络环境下会遇到连接不稳定的问题配置里改来改去很折腾。我的做法是统一走 TaoToken 的 API 通道一个 Key 管所有模型调用配置只写一次后面 Cron 任务触发时用的也是同一条通道不会出现手动对话能通、定时任务超时这种割裂。TaoToken 官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。注意 API 地址后面不加任何查询参数保持干净。第一步去控制台创建 API Key。打开 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 登录后在 API Keys 页面新建一个 Key复制出来。这个 Key 就是后面所有配置里要填的凭证建议单独建一个给自动化任务用方便出问题时单独吊销不影响你手动对话用的那个。第二步确认你要用的模型名。不同任务对模型的要求不一样定时轮询这种轻量任务用便宜快速的模型就够日报汇总这种需要一定理解能力的可以用强一点的。模型列表在文档里能查到接入说明看 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。第三步把 Key 写进环境变量别硬编码在配置文件里。这样配置文件可以进 gitKey 不会泄露# Linux / macOS写进 ~/.bashrc 或 ~/.zshrc export TAOTOKEN_API_KEYsk-你的key export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEY$TAOTOKEN_API_KEY# Windows PowerShell写进 $PROFILE $env:TAOTOKEN_API_KEY sk-你的key $env:ANTHROPIC_BASE_URL https://taotoken.net/api $env:ANTHROPIC_API_KEY $env:TAOTOKEN_API_KEY这里有个细节值得说Claude Code 读的是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY这两个变量所以我把 TaoToken 的 Key 赋给ANTHROPIC_API_KEY等于让 Claude Code 以为自己在连官方端点实际走的是 TaoToken 通道。这样 Cron 任务触发时模型调用和手动对话走的是同一条路行为一致。如果你更习惯用配置文件而不是环境变量Claude Code 的settings.json里也能指定。但环境变量的好处是切换方便而且不会把 Key 写进项目目录。我两种都用过最后留在环境变量方案上。3. 可复制的配置骨架settings.json 与 config.tomlClaude Code 的配置分两层一层是settings.json管运行环境和行为另一层是任务本身的参数通过 CronCreate 工具传入。很多人以为要手写 cron 表达式其实不用——你用自然语言告诉 Claude它帮你翻译。但理解参数含义能让你在排查问题时心里有数。先看settings.json的骨架。这个文件放在项目根目录的.claude/下或者用户级的~/.claude/下。项目级配置只对当前项目生效用户级对所有项目生效。我建议自动化任务相关的配置放用户级避免每个项目都要复制一遍{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key }, permissions: { allow: [ Bash(curl:*), Bash(git log:*), Read ] }, hooks: { PostToolUse: [ { matcher: Edit, hooks: [ { type: command, command: echo \$(date) edited\ .claude/hook_log.txt } ] } ] } }permissions.allow这一项很关键。Cron 任务触发时是无人值守的如果任务里要跑curl或git log而权限没放开任务会卡在权限确认上等于白设。把定时任务会用到的命令提前加进白名单是让自动化真正跑起来的前提。上面这个例子里我放开了curl和git log你可以按自己任务的实际需要增减。再看config.toml。如果你用的是某些支持 TOML 配置的客户端或包装层骨架长这样[api] base_url https://taotoken.net/api api_key sk-你的key timeout_seconds 120 [model] default claude-sonnet-4-20250514 fast claude-haiku-4-20250514 [cron] enabled true timezone Asia/Shanghai max_concurrent_jobs 3timezone这一项容易被忽略。cron 表达式默认按本地时间解释如果你的机器时区和你的预期不一致任务会在错误的时间触发。显式写上Asia/Shanghai能避免这类问题。max_concurrent_jobs限制同时运行的任务数防止多个任务挤在一起把通道打满。任务本身的参数通过 CronCreate 传入四个字段参数含义示例cron5 段表达式分 时 日 月 周*/10 * * * *每 10 分钟prompt到期时执行的描述检查 PR #42 是否合入recurringtrue 周期任务false 一次性truedurabletrue 存盘false 仅本次会话truecron 表达式速查五个字段从左到右是分、时、日、月、周*/5 * * * * 每 5 分钟 0 * * * * 每小时整点 0 9 * * * 每天 9:00 0 9 * * 1-5 工作日 9:00 30 14 28 2 * 2 月 28 日 14:30一次性 0 0 1 * * 每月 1 号零点4. 创建、触发、验证完整动作演示配置就绪后实际操作比想象中简单。你不需要手写 CronCreate 调用直接用自然语言对 Claude 说就行。下面走一遍完整流程。4.1 创建一个周期任务在 Claude Code 里输入每 10 分钟检查一次 https://taotoken.net/api 是否可达 如果连续两次失败就告诉我并附上最近一次的错误信息。Claude 会把它翻译成类似这样的调用CronCreate( cron: */10 * * * *, prompt: curl -s -o /dev/null -w %{http_code} https://taotoken.net/api如果返回不是 200 就报告状态码, recurring: true, durable: true )注意durable: true这样任务会写进.claude/scheduled_tasks.jsonClaude Code 重启后自动恢复。如果你只是临时试一下用durable: false会话结束任务就没了。4.2 查看已创建的任务直接问列出我所有的定时任务Claude 调用 CronList返回类似job_id: job_a1b2c3 cron: */10 * * * * prompt: 检查 https://taotoken.net/api 可达性 recurring: true durable: true next_run: 2025-06-01 14:30:00next_run是下一次触发时间用来确认时区和表达式是否符合预期。如果这个时间不对八成是时区问题回去检查config.toml里的timezone。4.3 验证任务真的会触发最稳的验证方法是创建一个一分钟后就触发的一次性任务看它是否按时执行。比如现在是 14:29你输入一分钟后提醒我检查今天的构建结果Claude 创建CronCreate( cron: 30 14 1 6 *, prompt: 提醒用户检查今天的构建结果, recurring: false, durable: false )等到 14:30Claude Code 空闲时会把这条 prompt 交给模型执行你会看到它主动发消息提醒你。如果没触发先确认 Claude Code 是否在运行、是否处于空闲状态——任务在 REPL 忙碌时会排队不会打断你当前的对话。4.4 删除任务验证完不需要的任务直接说把 job_a1b2c3 删掉Claude 调用 CronDelete 完成清理。周期任务即使不手动删也会在第 7 天最后一次触发后自动过期这是内置的保护机制防止忘记清理的任务无限跑下去。5. 本篇常见错误排查5.1 任务创建了但从不触发最常见的原因是 Claude Code 没在运行。Cron 是会话内的调度器进程不在任务自然不触发。如果你需要关掉终端也继续跑的效果那得用系统级 crontab 去定时拉起 Claude Code 的命令行模式而不是依赖内置 Cron。另一个原因是任务处于排队状态——你正在和 Claude 对话它优先处理当前交互定时任务会等空闲。5.2 触发时报权限错误任务里的命令没在白名单里。回到settings.json的permissions.allow把用到的命令加进去。比如任务要跑git log就加Bash(git log:*)。注意通配符的位置Bash(curl:*)表示允许所有 curl 调用写窄了会拦不住。5.3 时间对不上三个检查点一是config.toml里的timezone是否设置正确二是 cron 表达式是 5 段不是 6 段Claude Code 用的是标准 5 段格式没有秒字段三是 cron 本身有秒级抖动系统故意加了随机偏移来避免任务同时涌向通道所以不要指望它精确到秒。5.4 durable 任务重启后丢失检查.claude/scheduled_tasks.json是否存在且可写。如果这个文件被 gitignore 或者目录权限不对任务写不进去。另外确认创建时确实传了durable: true默认值是 false不传就不存盘。5.5 模型调用超时如果任务触发后卡住或报连接错误先手动跑一次同样的 prompt确认 TaoToken 通道本身是通的。手动能通、定时不通多半是环境变量没被 Claude Code 进程继承——比如你在 shell 里 export 了但 Claude Code 是从桌面图标启动的读不到。这种情况把配置写进settings.json的env字段更稳妥。6. 把定时任务接进你的自动化流程到这里创建、触发、验证、排障的闭环就走完了。回到最开始那个判断Claude Code 的 Cron 适合开发期间的自动化不适合替代服务器上的常驻调度。它的价值在于把需要理解上下文的重复检查交给 AI而不是把写死的命令再包一层。如果你打算长期用建议把模型通道固定下来别每次换。TaoToken 的 Coding Plan 适合需要长期跑编码和 Agent 任务的场景入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 一个 Key 覆盖对话和定时任务配置不用改来改去。想先手动验证模型行为再决定任务怎么写可以去模型对话页 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 试几条 prompt确认输出符合预期后再固化成定时任务。Key 的管理和轮换在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后留一个我实际在用的组合工作日早上 8:55 汇总昨天的 git 提交每 30 分钟检查一次部署端点每周一生成依赖升级清单。三个任务都设了durable: true权限白名单里放开了git log和curl。跑了两周唯一一次没触发是因为我关了 Claude Code 去开会——这恰好说明它的边界在哪也说明它该用在哪。