黑客松结束一个月后什么会坏掉,以及一位 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时,两个经典陷阱同时击中了我:
- 新密钥在 Vercel 中被限定到
Development,而Production和Preview仍持有 34 天前的旧密钥。 - 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 上的相关阅读
社区的两篇文章正好切中今天调试会话中的操作边界:
- [Vercel 上的“Invalid API key”或未定义环境变量:如何修复] 作者@unbreakkit详细讲解了环境变量作用域(
ProductionvsPreviewvsDevelopment)以及缺少重新部署如何导致401 Invalid API key即使在你粘贴了新密钥之后,Vercel 上仍会出现错误。 - [你的 AI 智能体运行数小时。它的 GitHub Token 可能不会。] 作者@__be2942592探讨了当代码假设环境中的 GitHub token 永远有效、而不是显式处理
401过期时会发生什么。
试一试
- 在线页面: readme-clew-2.vercel.app(点击USE DEMO REPO,然后SCAN)
- 源码和 478 个离线测试: github.com/earlgreyhot1701D/readme-clew-2
再次感谢Mikhail(@mansio)在真实仓库上发现了子字符串 bug,并提交了干净、经过充分测试的修复。
如果你是新来的,先快速了解一下背景。我在加州法院工作,负责一个县的法院运营。我从 2025 年 7 月开始用 AI 做开发,此后一直在公开学习。我不写代码。我负责指挥,智能体负责生成,我负责验证和决策。我构建了Clew Suite,一套公民科技工具,用于让复杂系统更容易被审视。这就是我写作的视角。
AI 辅助,人工批准。由 NLP 驱动。