怎么让 Claude 在你睡觉的时候干活

本文将从实际跑通的案例出发,拆解 Bash + OpenSpec + Claude 这套组合的完整运作逻辑。其中最关键的一环,就是引入Bash loop ------一种让 AI 在无人值守时依然能提交代码、跑测试、更新文档的工作方式。s

昨晚十点,我合上电脑下班回家睡觉。

今早打开终端,git log 一拉------十几条提交排着队,测试全绿,文档跟着更新了,多个需求按设计落了地。

不是有人加班,是我走之前启动的一个脚本------openspec.sh

我做某业务系统的时候,日常就是和各种数据源、配置表单、校验逻辑、决策流可视化打交道。这类工作有个特点:模式重复但组合多,边界清晰但需求量大。 写第一个需求需要想清楚,写第八个、第十五个就是在重复自己。

这几天我开始尝试让 AI 替我跑这些重复性工作。试了几轮之后发现确实可行,流程也逐渐稳定下来。这篇文章就讲讲我是怎么一步步搞出来的。

起因:Ralph Wiggum 与 ralph.sh

事情得从我刷到的一篇文章说起:Ralph Wiggum: How to Make Claude Code Work While You Sleep with a Bash Loop

名字来自《辛普森一家》里那个总说"I'm helping!"的小胖子 Ralph Wiggum。作者说,这个 Bash 循环就像他------你不太确定它到底在干嘛,但回头一看,活儿确实干了。

核心思路非常直接:

别搞什么多 Agent 编排、DAG 调度、分阶段状态机。 就一个 while 循环 ,反复调用 claude -p。 每一轮让 AI 自己找还没干完的任务,写代码,跑测试,提交。 循环跑到所有任务完成、或者到了你设的上限为止。

整个方案只要三个文件:

  • prd.json --- 需求 + 任务列表,每个任务有个 passes: true/false
  • progress.txt --- AI 的跨轮次记忆。每轮结束写一笔「做了啥、踩了什么坑」,下一轮开始先读它
  • ralph.sh --- 那个 while 循环,内部用 claude -p 把 prompt 传给 Claude Code

看完之后我觉得思路一下打开了:原来不需要搞得那么复杂。

之前我一直觉得让 AI 自动写代码得搞套很重的系统------任务编排、状态管理、失败重试......但其实一个 while 循环就能跑通。关键不在于调度有多精巧,而在于任务描述够不够清楚。

试着跑起来

ralph.sh 跑起来。 前提是你已经装了 Claude Code 且能用命令行 claude -p "..."。把项目里的 ralph.sh 打开,里面就是一串固定 prompt:读 prd.json 和 progress.txt,选第一个 passes: false 的任务,实现、跑测试、更新 prd、写 progress、commit,全部做完就输出 RALPH_COMPLETE。脚本每轮执行一次 claude -p,检查输出里有没有 RALPH_COMPLETE,有就退出,没有就 sleep 5 再下一轮。

首先是工具链。 prd.json 里每个任务只有一句话 description,需求一复杂就变得又臭又长。我需要更结构化的方式来写需求------ OpenSpec,可以用提案(proposal)把需求写清楚,AI 按提案执行。

progress.txt 做跨轮次记忆。 这是原版最聪明的设计:每轮结束 AI 往里追加「做了啥、踩了什么坑、下一轮建议」,下一轮先读再干活,不用靠对话历史,Token 也不会随轮次暴涨。

关于 git 提交。 原版 ralph.sh 的 prompt 里让 AI 自己执行 git add . && git commit ...。有的环境里 Claude 不会真执行 shell 命令,那就需要你在脚本里根据「本轮是否改动了文件」自己做一次 commit

于是整个流程就是这样:

白天想清楚,晚上让 AI 跑,第二天审结果。

用 OpenSpec 把 Plan 写厚

具体做法是在 prd.json 里给每个任务加一个 openspecChange 字段,指向一个 OpenSpec 变更目录(完整 prd.json 见[附录 A](#附录 A "#%E9%99%84%E5%BD%95-a-prdjson-%E7%A4%BA%E4%BE%8B"))。AI 拿到任务后,去这个目录下读一整套文件:

bash 复制代码
openspec/changes/add-frequency-rule/
├── proposal.md       # 需求背景、方案设计、影响范围、不能动的部分
├── tasks.md          # 可勾选的任务清单
└── specs/            # 本次变更的技术约定(字段校验、接口格式等)

白天用 /opsx-propose 和 AI 一起把这几个文件写好。这个过程本身就是在想清楚------很多模糊的地方,是在写 proposal 的时候才暴露出来的。写完之后,晚上的循环脚本用 claude -p "/opsx-apply xxx" 触发执行,AI 会自动读这套文件,而不是靠 prompt 里的一段文字。完整脚本见[附录 B](#附录 B "#%E9%99%84%E5%BD%95-b-openspecsh-%E5%AE%8C%E6%95%B4%E8%84%9A%E6%9C%AC")。

另外两个值得说的设计:

progress.txt 做跨轮次记忆。 这是 Ralph Wiggum 原版最聪明的部分。每轮执行完 AI 往里追加一段笔记------做了什么、遇到什么问题、下一轮建议怎么做。下一轮开始先读它。git log 只记录"改了什么文件",progress.txt 记录的是"为什么这么改"。第二天你翻它就能还原 AI 整晚的决策过程。

归档让规范累积。 变更完成后用 /opsx-archive 归档,过程中产生的新规范会同步到 openspec/specs/。比如第一次跑"代码对比功能"时,specs 里约定了"所有的代码对比功能,采用code-diff组件"。归档后这条规范进了全局。下次跑"类似代码对比"时,AI 自动知道要用同样的组件------不用你再说一遍。跑得越多,AI 的上下文越完整。

跑起来之后,做了哪些优化

刚跑通的那个版本其实挺粗糙的。这几天陆续踩了一些坑,也针对性做了优化。

怎么确认 AI 真的干完了

这是夜间无人值守最核心的问题:你怎么知道 AI 不是「觉得自己做完了」就停了,而是真的做完了?

ralph.sh 的做法 就是 Stop Hook :每轮跑完后在 AI 的输出里查找预定义的「通关密语」RALPH_COMPLETE;没有就继续下一轮,直到出现或达到轮数上限。prompt 里要求 AI:只有当 prd.json 里所有 任务的 passes 都变成 true 后,才在输出里写 RALPH_COMPLETE。如果你不放心「AI 说自己做完」,可以在脚本里加一层校验:检测到 RALPH_COMPLETE 后,用 jq 查一遍 prd.json,确认所有 passes 均为 true 再 exit,否则打 log 并继续循环。

我的做法是两层校验:验收用 OpenSpec 工具先验证任务是否全部完成,通过后再更新 prd.json;完成判定由脚本用数据把关。 在 prompt 里要求 AI:本变更做完后用 OpenSpec 工具(如 openspec status、跑 acceptance)验证该变更已 all_done,通过后再把该 userStory 的 passes 标为 true 并写出通关密语(如 TASK_COMPLETE)。脚本侧不信任「AI 说自己做完了」:只有当输出里出现通关密语 用 jq 查到 prd.json 里所有 userStory 的 passes 均为 true 时,才认为 PRD 真正完成并退出;否则会提示「Agent 报了完成但 prd 仍有未完成」并继续下一轮。

另外一个经验:给循环设一个硬性上限。 我一般设 50 轮。即使验证逻辑有漏洞导致循环不退出,最多跑 50 轮也会自动停。这是防止"夜间烧钱"的最后一道安全网。

怎么节约 Token 消耗

夜跑最容易被忽视的成本就是 Token。每轮迭代 AI 都要读上下文、生成代码、跑完流程------几十轮下来消耗很可观。

几个有效的优化:

1. 状态外部化,不靠对话历史。 这是 Ralph Wiggum 原版就有的设计------每轮迭代是一次独立调用,AI 通过读 progress.txt 和 prd.json 恢复上下文,而不是把之前几十轮的对话全塞进 prompt。这样每轮的 Token 消耗是恒定的,不会随轮次增长。

2. 任务粒度要小。 一个大任务拆成几个小任务,每个任务的 proposal 更短、specs 更聚焦,AI 每轮需要读的上下文就少。实测下来,三个小变更比一个大变更的总 Token 消耗还少------因为大变更里 AI 经常在一个问题上反复尝试。

3. 高风险任务放前面。 架构决策、核心抽象这类容易出错的活优先跑。如果第 3 轮就翻车了,你只浪费了 3 轮的 Token。反过来,如果把它放在第 30 轮,前面 29 轮的工作可能都白费。

4. 先白天 HITL 调通,晚上再 AFK。 HITL(Human In The Loop)就是你在旁边盯着跑几轮,看 AI 的执行路径对不对、prompt 需不需要调。确认没问题了,再放到夜间无人值守。不要第一次就直接 AFK(Away From Keyboard)------那是在拿 Token 赌运气。

Plan 的质量决定一切

跑了几轮之后最深的体会:夜间跑得好不好,完全取决于白天 Plan 写得好不好。

有一次 proposal 里只写了"参考模块 A 的拖拽组件,实现模块 B 的配置功能",没说清楚模块 B 的细节、不能动模块 A 的拖拽组件。AI 把整个模块 A 的全部功能搬到模块 B 功能中,还顺手改了模块 A 组件的拆分以及逻辑抽象。第二天看 diff 才发现改动范围远超预期。

从那以后总结了几条写 Plan 的规则:

  • 写清楚"不能动什么"比"要做什么"更重要。 AI 很擅长发散,但不知道哪些是禁区
  • 验收标准必须是可机器验证的。 "性能提升 30%"不行(怎么测?),"编写测试用例,测试通过,pnpm run lint, pnpm run build通过"可以
  • 一个变更只做一件事。 proposal 里如果写了"顺便把 XX 也重构一下",AI 真的会顺便重构,然后影响范围不可控

什么活适合夜跑

跑了几轮,初步有了手感:

特别适合的:

  • 重构(目标明确、有测试兜底)
  • 补测试(覆盖率从 40% 到 80%)
  • 补文档(按现有代码生成)
  • 依赖升级(升级 + 修 breaking changes)
  • 按明确 spec 实现的小功能
  • 代码风格统一(批量改命名、格式)
  • 框架迁移(Options API → Composition API、Jest → Vitest)

不适合的:

  • 需要产品现场拍板的交互设计
  • 全新业务逻辑(没有参考,AI 容易偏)
  • 成功标准模糊的探索性开发
  • 涉及"潜规则"没写进 specs 的改动

实际案例:变量类型功能的一次夜跑

说个具体的。我们某业务系统需要支持多种变量类型加工------类型 A、类型 B、类型 C、类型 D......每种类型逻辑类似(都有基本信息、配置信息、使用信息三个区块),但字段、校验逻辑、联动关系各不相同。

以前的做法:写完第一个类型 A,然后复制粘贴改成第二个(类型 B),再改成第三个(类型 C)......每次都在做「理解上一个 → 改字段 → 改校验 → 调联动 → 改测试」的循环。三天出一个,全部变量类型开发完成要两周多。

用夜跑方案之后,流程变了:

白天我做的事: 先把第一个变量类型(类型 A)完整写好,确认质量没问题。把组件结构、props 接口、校验逻辑、测试模式写进 prd 的 description 或单独 spec 文件;为每种类型在 prd 里写一条任务------写清楚「这种变量有哪些字段、校验逻辑是什么、和类型 A 的差异、和后端接口如何调用」。(若用 OpenSpec,就写成 proposal + tasks + specs。)

晚上 AI 跑的事: prd.json 里排好优先级,./openspec.sh 50 启动。Claude 按 prd 一条条做:读 progress.txt、实现、跑测试、更新 passes、写 progress、commit,直到全部完成或到轮数上限。

第二天我做的事: 审 diff 和 progress.txt。个别细节调一下,merge。

一晚上跑完了 3 个类型的变量组件,包括对应的校验逻辑和测试。手写的话,这个量至少要一两周。

这就是这类前端特别适合这套方案的原因:相似功能多、模式固定、边界清晰。 你只需要把第一个做好、把模式写进 specs,剩下的就是 AI 的强项。

类似的场景还有很多:

  • 文件下载&导入 --- 每种类型文件下载和导入不同,但下载和导入工具函数一样,写好 spec 就能批量生成
  • 多环境上线校验 --- 已有的校验覆盖不全,把缺失的校验场景列成 tasks,AI 一条一条补
  • 决策流可视化节点 --- 多种节点类型(条件节点、动作节点、分支节点......),每种的渲染和交互逻辑用 spec 描述清楚,批量实现

安全兜底

分支隔离。 AI 永远在独立分支上跑,绝不碰 main。万一结果不对,丢掉分支就行,不用回滚。

迭代上限。 前面说了,MAX_ITER 是最后的安全网。

第一次跑会有点不放心。 我中间看了几次 progress.txt。但结果还不错------小变更跑完了,commit message 写得很规范。后面就习惯了。建议第一次先拿一个小任务验证,确认流程没问题再逐步放量。

你也想试?从这里开始

不用一步到位。我也是从一个最小的变更开始验证的。

1. 装 Claude Code --- 确保本机能用 claude -p "hello" 调通(Claude Code 命令行)。

2. 准备三个文件 --- 把 openspec.sh 拷到项目根(或使用文末[附录 B](#附录 B "#%E9%99%84%E5%BD%95-c-ralphsh-%E8%84%9A%E6%9C%AC")),再建好 prd.json(见[附录 A](#附录 A "#%E9%99%84%E5%BD%95-a-prdjson-%E7%A4%BA%E4%BE%8B"))和空的 progress.txt

3. 先白天手动跑一个变更 --- 在 Claude 里输入 /opsx-propose,写一个小需求(比如"给某个模块补一组单元测试"),和 AI 一起把 proposal 和 tasks 打磨好,再用 /opsx-apply 执行。这一步不上脚本、不夜跑,就是验证 AI 能不能按你的 Plan 正确干完一个活儿。

4. 觉得靠谱了,上夜跑 --- 下班前 ./openspec.sh 50,第二天看 git log 和 progress.txt。

最后

试了几天,最大的感受不是「效率高了」------虽然确实高了------而是我干的事变了

以前一天:梳理需求 → 某模块的下载与导入优化 → 写代码 → 写校验 → 写测试 → 再来一个需求。 现在一天:梳理需求 → 想清楚需求如何做 → 写 Plan 和 Spec → 审代码 → 做架构决策。

重复性的实现交给 AI 夜跑,我把时间花在"梳理清楚需求,方案如何实现"上面。这反而让整体架构比以前更好了------因为你必须先想清楚方案,才能写出 AI 能跑的 spec。

Ralph Wiggum 那篇文章给了我一个关键的认知:自动化不需要复杂的系统。 一个 while 循环就够了。复杂度应该花在"想清楚要做什么"上面。

Claude-cli + OpenSpec 在这个基础上解决了规模化的问题:当任务不再是一两句话能说清楚的、当规范需要在多个变更之间传递------你需要比 prd.json 更结构化的东西。

但核心还是那个循环。还是那三个文件。还是那个一直在说 "I'm helping!" 的 Ralph。

相关链接:

附录

附录 A: prd.json 示例

json 复制代码
{
  "project": "task-manager-frontend",
  "version": "1.0.0",
  "description": "Frontend for task management application",
  "backendAPI": "task-manager-api",
  "_workflow": "openspec",
  "_openspecNote": "每个 userStory.openspecChange 对应 openspec/changes/ 下的目录名。openspec.sh 会按优先级执行对应 OpenSpec 变更,变更 all_done 后将该 story 的 passes 改为 true。",

  "techStack": {
    "language": "TypeScript",
    "framework": "React",
    "buildTool": "Vite",
    "stateManagement": "Zustand",
    "styling": "Tailwind CSS",
    "routing": "React Router",
    "httpClient": "Axios",
    "formHandling": "React Hook Form",
    "validation": "Zod",
    "testing": "Vitest + React Testing Library"
  },

  "userStories": [
    {
      "id": "FUS001",
      "title": "应用布局与关于页",
      "description": "标准应用布局(Header、左侧 Sidebar、主内容区、Footer),React Router 接入,首页与关于页路由,关于页展示项目简介、技术栈与使用说明",
      "acceptanceCriteria": [
        "MainLayout 含 Header、Sidebar、Outlet、Footer,路由 / 与 /about 可用",
        "Sidebar 含「首页」「关于」导航,点击切换路由并更新主内容区",
        "About 页展示项目名称、简介、技术栈、基本使用说明(静态内容)",
        "浏览器刷新后 / 与 /about 路由与内容正确"
      ],
      "priority": 1,
      "passes": false,
      "openspecChange": "home-layout-sidebar-about"
    }
  ],

  "projectStructure": {
    "src/": {
      "components/": "Reusable UI components",
      "pages/": "Page-level components",
      "hooks/": "Custom React hooks",
      "store/": "State management (Zustand)",
      "services/": "API service functions",
      "types/": "TypeScript type definitions",
      "utils/": "Utility functions",
      "styles/": "Global styles and Tailwind config"
    }
  },

  "constraints": [
    "Each component must have at least 3 tests",
    "Minimum test coverage: 80%",
    "No any in TypeScript",
    "Responsive design for mobile, tablet, and desktop",
    "Atomic commits for each feature",
    "每个 OpenSpec 变更完成后原子提交",
    "Accessibility compliance (WCAG 2.1 AA)"
  ],

  "setupInstructions": {
    "1": "应用布局与关于页"
  }
}

附录 B: openspec.sh 完整脚本

bash 复制代码
#!/bin/bash
# openspec.sh - Autonomous Coding Claude-cli 非交互 / 自动化模式)
# Usage: ./openspec.sh [max_iterations]

MAX_ITERATIONS=${1:-50}
ITERATION=0
COMPLETE_SIGNAL="TASK_COMPLETE"

echo "=== Openspec Coding Agent==="
echo "Max iterations: $MAX_ITERATIONS"
echo "Stop signal: $COMPLETE_SIGNAL"
echo ""

while [ $ITERATION -lt $MAX_ITERATIONS ]; do
    ITERATION=$((ITERATION + 1))
    echo ""
    echo "--- Iteration $ITERATION of $MAX_ITERATIONS ---"
    echo "Running claude -p (output below)..."
    echo ""

    # 保存当前 prd 状态,用于迭代后判断是否有"刚完成"的变更并自动提交
    PRD_BEFORE=$(mktemp)
    if [ -f prd.json ]; then cp prd.json "$PRD_BEFORE"; fi

    PROMPT="You are an autonomous coding agent. Read prd.json and progress.txt. Execute OpenSpec changes linked from PRD.

INSTRUCTIONS:
1. In prd.json, pick the first userStory with passes: false (by priority order). Let CHANGE_NAME = that story's \"openspecChange\" (folder under openspec/changes/).
2. Apply that OpenSpec change:
   - Run: openspec status --change \"CHANGE_NAME\" --json
   - Run: openspec instructions apply --change \"CHANGE_NAME\" --json
   - Read all contextFiles from the apply output (e.g. proposal, specs, design, tasks)
   - Implement each pending task from the change's tasks file; after each task, mark it complete (- [ ] -> - [x]) in the tasks file. Tasks marked optional (可选) may be skipped and marked complete to proceed.
   - If state is blocked or a task is unclear, add notes to progress.txt and do NOT write $COMPLETE_SIGNAL
3. After implementing: run pnpm test:run && pnpm build (or project's equivalent). If they fail, fix before continuing.
4. When the change is all_done (all tasks complete or optional ones skipped): run acceptance (test + build). If acceptance passes: set that userStory's passes to true in prd.json; add notes to progress.txt; commit: git add . && git commit -m 'feat: [openspec] CHANGE_NAME'
5. Only when EVERY userStory in prd.json has passes: true, write exactly: $COMPLETE_SIGNAL (do not write it if any story still has passes: false).

IMPORTANT: One OpenSpec change (one userStory) per iteration. Use openspec CLI; keep commits atomic."

    # 实时输出并同时写入临时文件,便于检测完成信号(避免只打印标题后"卡住"的错觉)
    RALPH_LOG=$(mktemp)
    claude -p "$PROMPT" 2>&1 | tee "$RALPH_LOG"
    OUTPUT=$(cat "$RALPH_LOG")
    rm -f "$RALPH_LOG"

    # 若 Agent 未执行提交,由脚本补做:有未提交变更且本轮有 story 从 passes:false 变为 true 时自动 commit
    if command -v jq &>/dev/null && [ -f prd.json ]; then
        COMPLETED_CHANGE=""
        if [ -f "$PRD_BEFORE" ]; then
            for id in $(jq -r '.userStories[]? | .id' prd.json 2>/dev/null); do
                passes_now=$(jq -r --arg id "$id" '.userStories[] | select(.id==$id) | .passes' prd.json 2>/dev/null)
                change_name=$(jq -r --arg id "$id" '.userStories[] | select(.id==$id) | .openspecChange // ""' prd.json 2>/dev/null)
                passes_before=$(jq -r --arg id "$id" '.userStories[] | select(.id==$id) | .passes' "$PRD_BEFORE" 2>/dev/null)
                if [ "$passes_now" = "true" ] && [ -n "$change_name" ] && [ "$passes_before" != "true" ]; then
                    COMPLETED_CHANGE="$change_name"
                    break
                fi
            done
        fi
        if [ -n "$COMPLETED_CHANGE" ] && [ -n "$(git status --porcelain 2>/dev/null)" ]; then
            echo ""
            echo "=== 自动提交(任务已完成但未 commit)==="
            git add .
            git commit --no-verify -m "feat: [openspec] $COMPLETED_CHANGE" 2>/dev/null && echo "Committed: feat: [openspec] $COMPLETED_CHANGE" || echo "Commit failed (e.g. hook or git version). Run manually: git commit -m 'feat: [openspec] $COMPLETED_CHANGE'"
        fi
    fi
    rm -f "$PRD_BEFORE"

    # Check for completion signal AND verify prd.json (avoid false positive from LLM)
    if echo "$OUTPUT" | grep -q "$COMPLETE_SIGNAL"; then
        # Verify that all userStories in prd.json have passes: true
        if command -v jq &>/dev/null; then
            ALL_PASS=$(jq -r '[.userStories[]?.passes] | if length == 0 then "empty" else (all | tostring) end' prd.json 2>/dev/null || echo "error")
            if [ "$ALL_PASS" = "true" ]; then
                echo ""
                echo "=== PRD COMPLETED! ==="
                echo "All tasks have been implemented."
                exit 0
            else
                echo ""
                echo "=== WARNING: Agent reported $COMPLETE_SIGNAL but prd.json still has unfinished tasks (passes: false). Continuing. ==="
            fi
        else
            # No jq: trust the signal only (original behavior)
            echo ""
            echo "=== PRD COMPLETED! ==="
            echo "All tasks have been implemented."
            echo "(Install jq to verify prd.json before accepting completion.)"
            exit 0
        fi
    fi

    # Pause between iterations (avoid rate limiting)
    sleep 5
done

echo ""
echo "=== Iteration limit reached ==="
echo "Check progress.txt for current status."
相关推荐
AEIC学术交流中心2 小时前
【快速EI检索】2026年人工智能与电气工程国际学术会议(AI-EE 2026)
人工智能
Yan-英杰2 小时前
远程控制软件哪个安全?2026 ToDesk/向日葵/RayLink加密、隐私与防护全面对比评测
网络·人工智能·网络协议·tcp/ip·http
恋猫de小郭2 小时前
让你的 OpenClaw 带你学习,清华开源 AI 私人导师 OpenMAIC
前端·人工智能·ai编程
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章37-区域截图
图像处理·人工智能·opencv·算法·计算机视觉
CCPC不拿奖不改名2 小时前
RAG基础:评测系统RAGAS的四大指标
linux·服务器·人工智能·计算机视觉·前端框架·知识库搭建
搬砖者(视觉算法工程师)2 小时前
通俗易懂的 Transformer 入门文章(第三部分):多头注意力深度剖析
人工智能
happyprince2 小时前
2026年03月18日全球AI前沿动态
人工智能
weixin_429630262 小时前
6.1 LCVAE-CNN:基于LCVAE的室内Wi‑Fi指纹CNN定位方法
人工智能·神经网络·cnn
MonkeyKing_sunyuhua2 小时前
中国传统企业怎么应对AI时代
大数据·人工智能