NemoClaw CI 故障分类指南:用有界日志与校验产物定位 GitHub Actions 失败根因
【免费下载链接】NemoClawRun agents like Hermes, LangChain Deep Agents, and OpenClaw more securely inside NVIDIA OpenShell with managed inference项目地址: https://gitcode.com/gh_mirrors/ne/NemoClaw
本指南讲解 NemoClaw 维护者技能 nemoclaw-maintainer-classify-ci-failure 的使用方法:如何从任意 NemoClaw 检出目录调用classify-ci-failure.mts,对单个失败的 GitHub Actions job 完成分类。你将掌握命令行参数与输出 JSON 结构、日志的有界截取与敏感信息脱敏机制、产物(artifact)的严格校验式读取流程,以及分类器围绕可信子进程边界构建的安全模型,从而把一次 CI 失败快速收敛为可执行的修复建议。
技能定位:一次 job、一份有界证据、一组分类结论
该技能面向 NemoClaw 维护流程中的高频场景:GitHub Actions 上某个 job 失败,需要在不扩散权限、不泄露密钥、不执行可疑内容的前提下,判定失败类型并给出下一步动作。其核心设计目标有三点:
- 只读不写:脚本全程只使用经过认证的
gh读取 GitHub 数据,不执行任何 GitHub 写操作; - 有界有据:日志、产物、分页、压缩/解压体积、条目数、路径数、文件读取数全部有上限,输出保持可审计;
- 安全边界清晰:产物仅作为数据解析、绝不执行,外部命令仅限固定路径的 Bash 与 GNU coreutils,
gh只允许在受信目录中解析。
技能元数据定义在 agents/openai.yaml,其默认提示词将该技能描述为“从有界证据中分类一个失败的 GitHub Actions job”,并可由用户显式调用(user_invocable: true)。
快速开始:运行分类器
在任意 NemoClaw 检出目录下直接运行(示例 job id 为占位符,请替换为真实值):
node --no-warnings \ .agents/skills/nemoclaw-maintainer-classify-ci-failure/scripts/classify-ci-failure.mts \ --workdir "$PWD" --job-id <job-id>脚本入口是 classify-ci-failure.mts,直接执行时解析process.argv并以 JSON 形式输出分类结果(成功路径输出JSON.stringify(..., null, 2)),任何失败路径都会以非零退出码结束并在 stderr 输出一条有界、脱敏的诊断信息。
命令行参数
| 参数 | 必填 | 说明 | 默认值 |
|---|---|---|---|
--workdir | 否 | NemoClaw 检出目录,子进程的工作目录 | 当前工作目录(process.cwd()) |
--job-id | 是 | 正整数形式的 GitHub Actions job ID | 无 |
--artifact-name | 否 | 需要额外检视的 GitHub Actions 产物名称 | 不读取产物 |
--max-lines | 否 | 返回日志的最大行数,取值1~500的整数 | 120 |
--clip-mode | 否 | 截断方向,head(保留开头)或tail(保留结尾) | tail |
从参数解析实现(classify-ci-failure.mts)可以看到若干硬性校验:参数必须以--name value成对出现、不允许重复选项、未知选项直接报错;仓库被固定为NVIDIA/NemoClaw,jobId必须是^\\d+$且不能为0;artifactName必须与自身 trim 后完全一致,且只允许[A-Za-z0-9_. -]{1,200}字符集。--max-lines的正则校验同样只放行 1~500 的整数。
运行环境前提
分类器要求运行在满足以下条件的 Linux NemoClaw 检出中(classifyCiFailure入口会首先检查process.platform !== "linux"并直接抛错):
- Node.js 22.19 或更高版本(使用
node --no-warnings启动); /proc文件系统(用于进程组监控);- Bash,以及 GNU coreutils 中的
dd、stat、tail、wc(按固定绝对路径调用); - 已认证的
gh,且必须位于受信目录(见下文安全边界)。
分类工作流:元数据 → 日志 → 产物 → 结论
分类器在 classifyCiFailureWithRuntime 中按固定顺序执行四步:
- 读取 job 元数据:
gh api repos/NVIDIA/NemoClaw/actions/jobs/<job-id>,得到 job 名称、run_id、状态与结论,用于后续产物清单定位; - 有界拉取 job 日志:将日志经
tail -c 4000000截取到私有临时目录,再tail -n 20000二次收敛,随后按“NemoClaw CI 失败特征签名”正则筛出关键行及其上下文; - (可选)校验式读取产物:当传入
--artifact-name时,分页枚举该 run 的产物清单、按元数据体积精确下载 ZIP、经严格解析后提取*.result.json失败记录; - 分类并输出:基于日志文本与产物中的进程信号/超时/退出码证据,产出
findings、categories与nextActions,最终返回 JSON。
输出 JSON 结构
成功(或部分成功)路径返回的结构包含以下顶层字段:
{ "jobId": "123", "repo": "NVIDIA/NemoClaw", "job": { "id": 123, "runId": 456, "name": "CLI tests", "status": "completed", "conclusion": "failure", "url": "..." }, "result": "classified | unclassified | log-error", "categories": ["test-failure"], "findings": [{ "type": "test-failure", "detail": "...", "suggestion": "..." }], "nextActions": ["..."], "artifact": null, "log": { "jobId": "123", "repo": "NVIDIA/NemoClaw", "pattern": "NemoClaw CI failure signatures", "code": 0, "truncated": false, "truncationNotice": null, "truncationReasons": [], "clipMode": "tail", "maxLines": 120, "selectedLines": 83, "returnedLines": 83, "omittedLines": 0, "matchedLines": 6, "stdout": "...", "stderr": "" } }result的取值语义:有发现时为classified;日志正常获取但没有命中任何已知签名时为unclassified;日志获取本身失败时(log.code !== 0)为log-error,此时只返回 job 与 log 字段,不产生分类结论。注意技能对unclassified的官方态度:它只是“有界证据下的未分类”,绝不等于“不存在已知原因”。
日志的有界截取与截断语义
日志读取是整个分类的信息基础,所有边界都硬编码在脚本中:
- 源头截取:
gh api .../logs的输出先经tail -c 4000000写入job.log,即源头最多保留 4 MB 尾部字节; - 二次收敛:
tail -n 20000生成job.tail.log,最多 20000 行;脚本用stat -c %s与wc -l记录字节数与行数,用于判定sourceTruncated; - 特征筛选:对每一行应用
logPattern正则(匹配FAIL、AssertionError、Test timed out、Process completed、SIGKILL、Source-shape、Source architecture、grew by、adds JavaScript、NEMOCLAW_、npm audit report、docs-review、Fern validation、hadolint、shellcheck、Nemotron等签名),同时叠加NPM_AUDIT_FAILURE_PATTERN与NPM_BOOTSTRAP_FAILURE_PATTERN两组 npm 专项正则;命中行的前后各 20 行被纳入上下文; - 输出裁剪:筛选结果再经过
projectText,逐行最长 4000 字符、总文本最长 4,000,000 字符;随后boundedText按--clip-mode收缩到 40,000 字符以内; - 截断可审计:
log.truncationReasons会逐条列出截断来源(source-log-bounded-before-filtering、selected-lines-exceeded-maxLines、selected-line-exceeded-4000-characters、selected-text-exceeded-40000-characters),并在truncationNotice中提示“不要假设被省略的日志行无关或不存在”。
此外,子进程 stdout/stderr 的捕获上限为 8,000,000 字符,超限时置overflow标记并在 stderr 追加提示(classify-ci-failure.mts)。
日志分类规则(findings 类型)
分类规则集中在 classify-ci-failure.mts,每条 finding 由type / detail / suggestion三元组构成,最多返回 20 条。主要签名如下:
| finding 类型 | 触发特征 | 建议动作 |
|---|---|---|
test-failure | AssertionError/Test timed out/Failed Tests/ Vitest 失败计数 | 在对应 Vitest 项目中运行失败的测试,检查首个断言或超时 |
onboard-entrypoint-growth | FAIL: src/lib/onboard.ts grew by N line(s). | 将新逻辑移入src/lib/onboard/,或让入口净增减非正 |
new-javascript-source | PR 新增.js源文件 | 新 Node 源码、测试、脚本一律使用 TypeScript |
source-architecture-budget | “Source architecture budget failed” | 减少跨边界 import/export,仅在实测债务下降时放宽限额 |
source-shape-budget | “Source-shape test budget” 失败 | 优先行为测试;否则修复文档化的 source-shape 契约 |
env-var-documentation | NEMOCLAW_*环境变量文档门禁失败 | 在要求的参考文档中补充该变量说明,或移除变量 |
reviewed-npm-audit | npm audit 门禁报告咨询漂移 | 判断是否为实时咨询漂移,或走安全流程更新基线 |
reviewed-npm-bootstrap | 固定 npm 归档的身份/完整性被拒绝 | 检查固定 npm 身份与下载归档,不改动咨询例外基线 |
docs-review-receipt | 文档作者评审回执失败 | 针对当前 commit 重跑评审并刷新两个隐藏 SHA 字段 |
docs-validation | Fern validation/check-docs/npm run docs | 运行npm run docs并修复路由、frontmatter 或 MDX 错误 |
hadolint | hadolint或DL\d{4}Dockerfile 诊断 | 修复诊断,或使用窄范围、经策略批准的 ignore |
shellcheck | shellcheck或SC\d{4}诊断 | 运行针对性 ShellCheck 与 shfmt 检查并修复 |
advisor-second-opinion | PR 评审顾问 job 中的 Nemotron 第二意见失败 | 除非主顾问或维护者确认具体阻塞点,否则仅作参考 |
这些签名与仓库中真实存在的质量门禁一一对应,例如 source-architecture.mts 维护“源码架构预算”,run.mts 引用src/lib/onboard.ts的入口增长检查,reviewed-npm-audit对应 ci/npm-audit-exceptions.json 的例外基线管理。
产物(artifact)的校验式读取
当传入--artifact-name时,分类器进入产物检查路径,其边界设计如下(classify-ci-failure.mts):
- 清单分页有界:按
per_page=100分页枚举repos/NVIDIA/NemoClaw/actions/runs/<run_id>/artifacts,最多 20 页、最多 2000 个条目;total_count在分页前后必须一致,否则判定为分页不完整;同名产物(如重复上传)会被视为歧义并报错,列出前 20 个匹配 ID; - 体积有界:产物压缩体积必须 ≤ 25,000,000 字节(25 MB)且与元数据一致;下载流用
dd bs=65536 count=381与dd bs=1 count=30784精确截取 25,000,000 字节,再用dd bs=1 count=1做 1 字节探测,探测到额外字节即判定“超出压缩流限制”; - ZIP 严格校验:压缩包字节数须与元数据完全相等,随后交给 readValidatedArtifactZipEntries 解析——该函数完整校验 ZIP 结构(EOCD、中央目录、本地文件头、data descriptor、CRC-32),只接受 store/deflate 两种压缩方式,拒绝符号链接、特殊文件、重复路径、
../绝对路径等不安全条目,并在单次调用限制maxEntries(此处为 100)与maxTotalUncompressedBytes(此处为 100 MB)后才返回条目; - result.json 有界读取:只提取
*.result.json条目,单文件 ≤ 1,000,000 字节、≤ 2000 行;解析出exitCode、signal(须为系统已知信号名)、timedOut、error字段,最多保留 20 条失败记录、20 个畸形路径,并汇总malformedResultCount、filesRead; - 产物胜出者:多个失败记录按
artifactResultRank排序(signal > timedOut > error > 非零 exitCode)选出一个“胜出者”,转换为process-signal、process-timeout、artifact-reported-error、process-exit-code四类 finding 之一,并给出“先检查捕获命令与周边资源证据,再决定是否重试同一 commit”之类的建议。
测试夹具 classify-ci-failure.test.ts 通过伪造gh、dd、wc与真实 ZIP 构造覆盖了上述路径,包括元数据/日志/产物拉取的阻塞与失败注入、重复产物歧义、流超限、FAIL_PROBE_DD/FAIL_PROBE_WC等探针失败场景,验证了有界读取与终止排水(drain)行为。
安全边界:可信子进程与脱敏输出
分类器把自身定义为“内部可信子进程边界”,在 classify-ci-failure.mts 的注释中明确:调用方只能选择固定的gh、Bash 与 coreutils 操作,产物内容仅作为数据解析、从不执行,进程组管理只包含这些可信子进程,而不是不可信负载沙箱。具体机制包括:
- 固定可执行路径:
bash、dd、stat、tail、wc固定为/usr/bin/*;gh只允许在/usr/bin、/usr/local/bin、$HOME/.local/bin三个根目录下解析。解析前会逐组件校验:路径根不得是符号链接、每个组件必须是目录/普通文件、不得是符号链接、mode & 0o022必须为 0(组/其他不可写)、属主必须是 root 或当前用户、最终文件必须可执行(X_OK),且realpath结果不得逃出受信根(classify-ci-failure.mts); - 环境变量白名单/黑名单:子进程只继承
GH_CONFIG_DIR、GH_TOKEN、GITHUB_TOKEN、HOME、LANG、LC_ALL、LC_CTYPE、NO_COLOR、TERM、XDG_CONFIG_HOME十个变量,并显式剔除BASH_ENV、ENV、GH_ENTERPRISE_TOKEN、GH_HOST、NODE_OPTIONS、NODE_PATH、PATH、PERL5OPT、PYTHONHOME、PYTHONPATH、RUBYOPT等可劫持或泄露凭据的变量(classify-ci-failure.mts); - 多层脱敏:所有进入诊断或输出文本的内容都经过
redact(),覆盖 JSON 敏感字段、authorization/cookie/set-cookie等请求头、URL 内嵌凭据、X-Amz-*/sig/access_token等查询参数、KEY=value赋值,以及 Slackxox*/xapp-*、OpenAIsk-*、NVIDIAnvapi-/nvcf-、npmnpm_、gh[pousr]_、github_pat_等独立令牌形态,统一替换为[REDACTED](classify-ci-failure.mts);测试文件在REDACTION_CASES中为这些形态逐一断言了脱敏行为; - 私有临时目录:临时文件统一放在
/tmp/nemoclaw-ci-classifier-<uid>下(模式0700,运行时校验非符号链接、非组/其他可访问、属主为当前用户),日志与产物分别使用nemoclaw-ci-log.与nemoclaw-ci-classify.前缀的mkdtemp子目录;正常与失败路径都会尝试直接删除,删除失败则输出有界、脱敏的rm -rf -- <路径>修复命令并以非零退出; - 稳定进程组管理:每条命令都运行在
node -e包装的独立、detached进程组下,组领导者在所有后代退出前保持存活(通过轮询/proc统计组内成员);超时先发SIGTERM、1 秒后升级SIGKILL;收到SIGHUP/SIGINT/SIGTERM时拒绝新命令、终止并排空所有自有进程组、同步清理被跟踪目录,并以约定取消码(129/130/143)退出。
失败语义与 GitHub 访问硬停
技能的失败处理契约非常明确(SKILL.md):
- 任何非零的日志获取结果都是分类失败:清理尝试之后脚本以非零退出、不输出成功 JSON,只报告有界且脱敏的诊断;临时目录删除失败同样导致非零退出并附带直接的删除命令;
- GitHub 认证/授权失败必须停止:脚本会识别
authentication、authorization、forbidden、http 401、http 403、sso等关键字并抛出专用错误,要求先运行gh auth status,再请用户修正gh访问(含 SSO 或 token scope)后重跑;这一行为与仓库共享规则 git-github-hard-stop.md 一致——不得绕过访问错误、不得改凭据、不得回退到未认证通道; - 超时即失败:三类 GitHub 操作各有固定超时(job 元数据 30 秒、job 日志 60 秒、产物读取 60 秒,测试注入可通过
timeouts覆盖),超时会先终止进程组再抛“GitHub 不可用,请检查后重试”的错误。
小结:何时使用、如何配合其他维护技能
classify-ci-failure适合作为 CI 排障链的第一步:先用它拿到有界的日志切片、产物失败记录与分类建议,再结合 NemoClaw 仓库中的真实门禁(source-architecture.mts、run.mts、ci/npm-audit-exceptions.json)与 e2e 维护技能 的本地复现手段定位并修复问题。需要特别记住的三条原则:
- 证据有界:所有输出都经过截断与脱敏,截断原因随结果返回,阅读时必须结合
truncationReasons判断证据完整性; - 只读可信:脚本不做任何 GitHub 写操作,产物永不执行,外部命令全部来自受信路径;
- 失败即停:GitHub 访问类失败绝不绕过,先修正
gh认证(含 SSO/token scope)再重试;unclassified只代表有界证据下未命中已知签名,不代表不存在根因。
【免费下载链接】NemoClawRun agents like Hermes, LangChain Deep Agents, and OpenClaw more securely inside NVIDIA OpenShell with managed inference项目地址: https://gitcode.com/gh_mirrors/ne/NemoClaw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考