夜雨聆风学习资料网

ARTICLE · 1283661

OpenClaw 一天提交 627 次:闸门才是本事

OpenClaw 一天提交 627 次:闸门才是本事

📅 2026-10-30

周一早上打开电脑,我发现一件挺离谱的事:OpenClaw 的作者 Peter Steinberger,有一天往代码仓库里提交了 627 次。

我第一反应是"这人是不是不睡觉"。第二反应是"这数据准吗"。所以我当天用 GitHub 公开 API 自己数了一遍——627,一次不差。但真正让我坐直的,不是这个数字,是数完之后看到的另一组数。

▎ 01 · 627 次提交里,只有 2 次是"做新东西"

我写了个脚本,把 2026-02-22 那天他所有的提交信息拉下来自己分类:

# 数某人某天提交了多少次,以及每次属于什么类型

python3 - <<'EOF'

import urllib.request, json, re, time, collections

UA = {"User-Agent": "my-script", "Accept": "application/vnd.github+json"}

def api(url):

fora in range(4):

try:

r= urllib.request.urlopen(

urllib.request.Request(url,headers=UA), timeout=30)

returnr.status, json.loads(r.read()), r.headers.get("Link")

exceptException as e:

ifa == 3: raise

time.sleep(4)

day = "2026-02-22"

msgs, page = [], 1

while True:

st,d, link = api(f"https://api.github.com/repos/openclaw/openclaw/commits"

f"?author=steipete&since={day}T00:00:00Z&until={day}T23:59:59Z"

f"&per_page=100&page={page}")

forc in d:

msgs.append(c["commit"]["message"].split("\n")[0])

m= re.search(r'[?&]page=(\d+)[^>]*>; rel="next"', link or "")

ifnot m: break

page= int(m.group(1))

def typ(msg):

mm= re.match(r'^(\w+)(\([^)]*\))?(!)?:', msg)

returnmm.group(1) if mm else "other"

c = collections.Counter(typ(x) for x in msgs)

print("总数:", len(msgs))

for k, v in c.most_common():

print(f"{k}:{v}({100*v/len(msgs):.1f}%)")

EOF

跑出来的分布是这样的:

提交类型

次数

占比

test(测试)

319

50.9%

fix(修复)

144

23.0%

refactor(重构)

81

12.9%

docs(文档)

36

5.7%

perf(性能)

35

5.6%

chore(杂务)

8

1.3%

feat(新功能)

2

0.3%

build / style

2

0.3%

一天 627 次提交,新功能只有 2 次。剩下 625 次,全是在给已有的东西加固:写测试、修 bug、重构、补文档。

而且那天的提交信息里,Merge 开头的合并提交是 0 次。

▎ 02 · 真正的分水岭:过不了测试就不提交

我一开始以为这是"AI 手快"。直到我把他那天的提交间隔也统计出来,才看懂这里的差别。

相邻两次提交的中位间隔是 64 秒,最短 0 秒(同一秒提交两次),最长 24388 秒(约 6.8 小时),当天活跃跨度 26.15 小时。

更关键的是时段分布:

UTC 时段

提交次数

00 点

41

01–06 点

0

07 点

49

11 点

66

23 点

25

凌晨 0 点有 41 次提交,然后 1 点到 6 点整整齐齐的 0 次。这不是人在熬夜——这更像是机器在跑一个批次,跑完了就停,人不在旁边盯着。

那"手快"到底意味着什么?它意味着每一次提交背后,都有一道机器能自己判断的闸门:改完代码,自动跑测试,测试不过就不提交,改到过了才提交。

这就是"627 次提交不等于 627 次翻车"的原因。数量大不是因为乱,而是因为每次都先过了关。

▎ 03 · 我们自己的流水线:同样一分钟一次,结果完全不同

看完他的数据,我第一反应是去翻我们自己那台机器。我们也有一套 OpenClaw 流水线在跑,每天定时起一堆任务。

我写了个脚本直接读 OpenClaw 的状态库(只读打开,不动正在跑的东西):

# 数清一条 AI 流水线一天跑了多少次任务、失败多少次

import sqlite3, collections, datetime, os

DB = os.path.expanduser("~/.openclaw/state/openclaw.sqlite")

con = sqlite3.connect(f"file:{DB}?mode=ro", uri=True)# 只读,绝不改动

rows = con.execute(

"selectcreated_at, status, tool_use_count from task_runs").fetchall()

def ts(v):

v= int(v)

returndatetime.datetime.fromtimestamp(v/1000.0 if v > 1e12 else v)

per_day, per_fail = collections.Counter(), collections.Counter()

for created_at, status, _ in rows:

d= ts(created_at).strftime("%Y-%m-%d")

per_day[d]+= 1

ifstatus != "succeeded":

per_fail[d]+= 1

for d in sorted(per_day)[-7:]:

t,f = per_day[d], per_fail[d]

print(f"{d}总{t:4d}失败{f:3d}成功率{100*(t-f)/t:5.1f}%")

结果是:

日期

总任务

失败

成功率

2026-10-26

151

23

84.8%

2026-10-27

227

52

77.1%

2026-10-28

328

41

87.5%

10-28 那天跑了 328 次,跨度 22.2 小时,相邻任务中位间隔 65 秒。

跟那位作者的 64 秒,基本一样。

节奏一模一样,结果完全两样。 他的 627 次提交全部过闸门,我们的 328 次里有 41 次是失败的。更刺眼的是定时任务这一层:

总回执 526成功 415失败 111错误率 21.1%

34模型限流/过载

30任务超时

23agent数据库锁

7投递目标歧义

▎ 04 · 最贵的一个教训:脚本跑通 ≠ 老板收到

我接着去查真正投递出去的东西——不是脚本有没有 exit 0,是老板的飞书里到底有没有那个文件。

查法很直接,读会话数据库,看每条定时任务的最后一条助手消息里到底有没有附件行:

# 定时任务"跑完了"和"老板真收到了"是两件事

import sqlite3

con = sqlite3.connect(

"file:/Users/wuliaott/.hermes/profiles/xiaohao/state.db?mode=ro",uri=True)

rows = con.execute("""

selects.title, s.end_reason,

(selectcount(*) from messages m

wherem.session_id = s.id and m.role = 'assistant'

andm.content like '%MEDIA:%') as media_n

fromsessions s

wheres.title like '%日更%' or s.title like '%撰写%'

""").fetchall()

ok = sum(1 for r in rows if r[2] > 0)

print(f"定时任务 {len(rows)} 次,真正带附件交付 {ok} 次,未交付率 {100*(len(rows)-ok)/len(rows):.1f}%")

结果是:107 次定时任务,只有 83 次真正把文件送到了老板手里,24 次没有——未交付率 22.4%。

更气人的是,这 24 次里有 22 次的 `end_reason` 写着 `cron_complete`,也就是调度器认为"任务圆满完成"。

那 22 次断在哪?看最后一条消息,全是同一句:

`[CRON_FAILURE]` 本轮未完成文章交付:达到最大工具调用轮次……

模型在跑完最后一步之前断流了。产物可能全在磁盘上,脚本可能 exit 0,但那一行附件指令从来没被抄进最终回复,所以老板那边零感知。

这就是他 627 次提交和我们 328 次任务之间真正的差别:他的每一次提交都被"测试通过"卡着;我们的大部分任务没有任何东西卡着。

▎ 05 · 抄作业:三步给你的流水线装上闸门

回到最初的问题——我们能不能把"过不了闸就不算完成"这套搬过来?能,而且不用重写系统。

第一步:给每个定时任务加一个硬性验收门。

别让"跑完了"由模型自己宣布。写一个校验脚本,让它在最后一步必须返回非零退出码:

#!/usr/bin/env bash

# verify_delivery.sh —— 挂在定时任务最后一步

set -u

DOCX="$1"

if [ ! -s "$DOCX" ]; then

echo"FAIL: 成品不存在或为空 ($DOCX)" >&2

exit1# ← 非零退出 = 本轮不算完成

fi

python3 verify_docx_clean.py "$DOCX" --expect-images 3 || exit 1

python3 gzh_gate.py draft.md --account ai --images 3 --docx "$DOCX" || exit 1

echo "PASS: 全部门禁通过"

exit 0

第二步:把"交付"当成产物本身来验,而不是当成日志。

判断有没有送到,只有两种可靠办法——要么查最终回复里有没有附件行,要么查收件方的会话记录。别用"脚本有没有报错"当交付凭证。上面那段 media_n 的 SQL 就是直接查收件记录,比任何自我报告都硬。

第三步:让失败可读,别让它静默。

把错误分成三类分别记:模型限流(34 次)、任务超时(30 次)、数据库锁(23 次)。这三类的解法完全不同——限流要错峰、超时要拆任务、数据库锁要串行化。混在一起叫"失败了",你永远查不出该改哪。

我们自己的失败率是 21.1%,但每一类都能对上号。能对号入座的失败,才叫可用的失败数据。

▎ 避坑 · 三条别这么用

一、别把"提交次数多"当成效率高。 那 627 次里 319 次是测试,只有 2 次是新功能。如果你的流水线每天产出 300 个任务,但其中一半是失败重试,那你在跑噪音,不在跑流水线。先看类型分布,再看总数。

二、别用 `exit 0` 当成"交付成功"。 这是我们踩得最贵的一个坑。调度器说 cron_complete、脚本说成功、文件躺在磁盘上——三样都成立,但老板什么都没收到。验收要验最终产物和收件记录,不要验执行过程。

三、别让定时任务在没人盯着的时候自己跑长任务。 那位作者的流水线之所以敢在凌晨 0 点跑 41 次,是因为测试会替他把关。我们那 22 次断流,都是在长链路跑完前的最后一口气断的。跑不完的任务,宁可拆短、留检查点,也别指望它一口气跑到终点。

▎ 结尾

说句实话,我们这套跑了一天 328 次,还是有 41 次失败、22 次没送到。我不敢说抄到了他的水平。

但有一件事变了:现在每一次失败,我都知道它卡在限流、超时还是数据库锁,知道该改哪儿。 这比"看起来在跑"值钱多了。

所谓 AI 流水线的差距,从来不在一天跑了几百次,而在于每次跑完,你能不能拿得出一份"确实送到了"的证据。

如果你也想给自己的定时任务装一道闸门,最快的办法是:先写那个返回非零退出码的校验脚本,再把它挂到任务最后一步。就这一件事。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料