黑客松结束一个月后什么会坏掉,以及一位 DEV 同行作者在验证器里发现了什么。

TL;DR:上线一个月后README Clew 2,我打开它,结果每次扫描都失败了。确定性验证器没有问题,Jev on Glasser 也没有问题。相反,是三个小的运维陷阱同时凑到了一起:一个只读取公开仓库的工具上过期的 30 天 GitHub token,一个被轮换、作用域仅限于Development的 Claude Haiku 密钥(在 Vercel 中),以及一个子串 bug,由 DEV 同行作者Mikhail(@mansio)发现并修复于PR #2。

十月,我构建了README Clew 2,用于 AdaL 的“What Would Jev Do?”黑客松。它会检查一个公开的 JavaScript 或 TypeScript 仓库的 README 是否与其代码相符,把每一行可检查的内容归入四个桶:矛盾、缺失、无法验证,以及已验证。

这个构建的核心规则是模型永远不决定什么是真的:

任务 由谁来做
把 README 拆成行,并处理明显的标题/徽章 确定性代码规则
标注一行散文式文字做出的是哪种声明(命令、依赖、环境变量、文件/URL) Jev(jev-1.13.0)经由Glasser,置信度门槛为 0.8
判定为已验证、矛盾或缺失 五个确定性代码验证器
根据计数写两句平实的英文摘要 Claude Haiku(claude-haiku-5-5),失败时放行

当你把这么多活动部件(AdaL、Jev、Glasser、Haiku、GitHub 和 Vercel)接在一起,一个月后回来看到红色的SCAN FAILED横幅时,乍一看很难判断是哪一块坏了。

以下是实际出错的地方,以及由此产生的两个修复。


1. 当"javascript"隐藏了"ava":@mansio的 PR #2

在我还没来得及处理部署密钥之前,Mikhail(@mansio)就已经提交并合并了PR #2,这是该项目第一个外部代码贡献。

Mikhail 正在用真实仓库测试 README Clew 2,并在sindresorhus/yocto-queue上注意到了一些奇怪的地方。该仓库使用ava作为测试运行器,而 README 从未提到ava。覆盖率验证器本应把ava标记进缺失桶。但它却保持沉默。

为什么?因为 README 中包含一个指向 Mozilla 文档的链接:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

而在单词javascript内部,以小写形式,藏着三个字母的子串ava。

我们的覆盖率检查使用的是普通子串匹配:

// 之前:任何子串匹配都算作“在 README 中被提到”
if (readme。includes(name。toLowerCase())) continue;

更糟的是,同样的子串检查还存在于依赖校验器的文本防护逻辑中(inCodeContext),它用于检查候选包名是否出现在安装行上。像can在npm i canvas-parser中,或lodash在npm i lodash-es中这样的短名称,可能绕过文本防护并到达Contradicted,这正是该防护逻辑本应防止的误报。

修复方案

Mikhail 将两处子串检查替换为一条完整的词元规则,mentionsWord(),其边界由 npm 自身的包名字符(A-Za-z0-9_-)加上@和/来界定:

export function mentionsWord(text, name) {
  if (!name) return false;
  const esc = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  return new RegExp(`(?<![A-Za-z0-9_@/-])${esc}(?![A-Za-z0-9_-])`, 'i').test(text);
}

有了这一改动:

  • We use ava.和Tests run with \ava.仍然算作真实提及。
  • mozilla.org/.../javascript/...不再隐藏ava。
  • npm i lodash-es不再将lodash视为已安装包的声明。

Mikhail 用一个 27 个用例的测试矩阵和tests/mentions-word.test.js中的校验器控制项交付了该修复,首次尝试即符合仓库的文件大小上限和离线测试规则。如果你还没读过他在 DEV 上的工程现场笔记(比如The Dataset Was Lying: 4 of 6 "False" Facts Were True),去读读吧。


2. 30 天定时炸弹:向公共 API 发送过期令牌

Mikhail 的 PR 合并后,我仍有一个线上站点拒绝扫描。

每次点击SCAN都会返回HTTP 502:

{"error":{"code":"github_forbidden","message":"GitHub returned HTTP 401"}}

讽刺的是:README Clew 2 只扫描公共仓库。它每次扫描会向api.github.com发起 4 次元数据调用,并从raw.githubusercontent.com获取文件。对于低流量的公共读取,你甚至不需要GITHUB_TOKEN(GitHub 允许每个 IP 每小时 60 次未认证 REST 调用,或每小时 15 次完整扫描)。

在 10 月的黑客松期间,我添加了一个 30 天有效的 GitHub Personal Access Token,这样重复的测试扫描就不会触发未认证速率限制。三十天过去了,令牌过期了,而lib/github.js仍在每个请求上发送Authorization: Bearer <expired_token>。

GitHub 允许不带任何 Authorization头的请求,但会立即拒绝携带过期 Authorization头的请求,并返回401 Bad credentials。由于第 1 步抛出了GithubError,流水线在 Jev 或 Haiku 运行之前就停止了。

4 行修复

首先,我从 Vercel 移除了过期的GITHUB_TOKEN。其次,我们加固了lib/github.js,以便如果.env或服务器环境变量中存在过期令牌,且 GitHub 返回401,客户端会从共享 headers 对象中移除authorization头,并以未认证方式重试:

if (res.status === 401 && headers?.authorization) {
  delete headers.authorization; // 过期或无效的 token:公开仓库无需它也能工作
  return request(url, { fetchImpl, headers, as });
}

因为headers在 4 次元数据调用之间共享fetchRepo(),丢弃headers.authorization在第一次401意味着剩余的 3 次调用会直接发出,不带那个无效的 token。


3. 静默 fail-open + Vercel 环境作用域

最后一块是 Claude Haiku。按设计,Haiku 在 README Clew 2 中是fail-open的:如果摘要调用失败或超时,报告仍会完整渲染,clew 吉祥物会回退到按情绪固定的单行摘要。

我 10 月的 Anthropic 密钥也已被轮换。当我在 Vercel 中添加新的ANTHROPIC_API_KEY时,两个经典陷阱同时击中了我:

  1. 新密钥在 Vercel 中被限定到Development,而Production和Preview仍持有 34 天前的旧密钥。
  2. Vercel 环境变量的更改只会在下一次部署时生效。

运行vercel env ls立刻让这种分裂变得可见:

变量 环境 创建时间
ANTHROPIC_API_KEY(新) Development 今天
ANTHROPIC_API_KEY(旧) Production, Preview 34 天前
GLASSER_API_KEY Production, Preview, Development 34 天前

覆盖ANTHROPIC_API_KEY在所有三个环境中(Production, Preview, Development)并运行vercel --prod让 Haiku 摘要和 bucket 注释在第一次扫描时就恢复了。与此同时,Glasser 和 Jev(jev-1.13.0)从未出过问题:全部 4 次批量调用在 1.37 秒内完成,花费约半美分($0.0055),而$10.80的原始黑客松余额仍未动用。


DEV 上的相关阅读

社区的两篇文章正好切中今天调试会话中的操作边界:


试一试

再次感谢Mikhail(@mansio)在真实仓库上发现了子字符串 bug,并提交了干净、经过充分测试的修复。


如果你是新来的,先快速了解一下背景。我在加州法院工作,负责一个县的法院运营。我从 2025 年 7 月开始用 AI 做开发,此后一直在公开学习。我不写代码。我负责指挥,智能体负责生成,我负责验证和决策。我构建了Clew Suite,一套公民科技工具,用于让复杂系统更容易被审视。这就是我写作的视角。

AI 辅助,人工批准。由 NLP 驱动。