返回博客列表

OpenClaw PR #80773:config unset dry-run 功能实现

为 openclaw config unset 命令添加 --dry-run、--json、--allow-exec 标志,通过 ClawSweeper 八轮审查。包含 SecretRef fallout 检测、使用场景详解、审查流程分析。

#OpenClaw#PR#Open Source#TypeScript

功能概述

本 PR 为 openclaw config unset 命令添加了三个标志:

标志功能
--dry-run预览删除结果,不实际修改配置文件
--json结构化输出,便于脚本解析(需配合 --dry-run
--allow-exec在 dry-run 模式下允许执行命令验证 SecretRef

这些功能使 config unset 与兄弟命令 config setconfig patch 功能对等。

Issue: #80721 PR: #80773(opens in a new tab)


核心价值

安全价值:SecretRef Fallout 检测

OpenClaw 使用 SecretRef 在配置中引用密钥,而不是直接写入明文:

{
  "channels": {
    "slack": {
      "appToken": {
        "source": "env",
        "provider": "env-provider",
        "id": "SLACK_APP_TOKEN"
      }
    }
  },
  "secrets": {
    "providers": {
      "env-provider": { "type": "env" }
    }
  }
}

如果删除 env-providerchannels.slack.appToken 的引用就失效了——这就是 fallout(放射性沉降):一个删除操作影响了其他配置。

没有 fallout 检测的后果:

openclaw config unset secrets.providers.env-provider
# 删除成功,但静默失败
 
# 后续 Slack 集成不工作,用户不知道原因

有 fallout 检测:

openclaw config unset secrets.providers.env-provider --dry-run
# 输出:
# Dry run failed: 1 SecretRef could not be resolved
# - env:env-provider:SLACK_APP_TOKEN -> provider not found
#
# 用户知道需要先修改 channels.slack.appToken 的引用

功能价值

场景解决的问题
生产环境变更删除前预检,避免配置错误导致服务中断
CI/CD 集成通过 --json 输出实现自动化验证
Exec Provider--allow-exec 控制是否执行外部命令验证

使用场景

场景一:删除前预检

# 先验证删除是否会影响其他配置
openclaw config unset secrets.providers.env-provider --dry-run
 
# 输出:
# Dry run failed: 1 SecretRef assignment(s) could not be resolved.
# - env:env-provider:API_KEY -> SecretProviderResolutionError
#
# 说明 channels.slack.appToken 还在引用 env-provider,不能直接删除

场景二:脚本自动化

result=$(openclaw config unset tools.alsoAllow --dry-run --json)
 
if echo "$result" | jq -e '.ok' > /dev/null; then
  openclaw config unset tools.alsoAllow  # 验证通过,执行真实删除
else
  echo "删除失败: $(echo "$result" | jq -r '.error')"
  exit 1
fi

场景三:Exec Provider 检查控制

exec 类型 SecretRef 原理:

exec 类型的 provider 会执行外部命令获取密钥:

{
  "secrets": {
    "providers": {
      "aws-provider": {
        "type": "exec",
        "command": ["aws", "secretsmanager", "get-secret-value", "--secret-id", "MY_KEY"]
      }
    }
  }
}

OpenClaw 运行时执行该命令,从输出中提取密钥值。

为什么 dry-run 默认不执行:

原因说明
性能外部命令可能很慢(如网络请求)
副作用命令可能有副作用(写日志、发请求)
成本云 API 调用可能产生费用

使用方式:

# 默认:跳过 exec 类型验证(不执行外部命令)
openclaw config unset secrets.providers.aws-provider --dry-run
# 输出:skipped 1 exec SecretRef resolvability check(s)
 
# 显式授权执行:
openclaw config unset secrets.providers.aws-provider --dry-run --allow-exec
# 会真正执行 aws 命令来验证密钥是否可获取

这是"最小权限"原则——默认不执行,用户确认后才执行。


ClawSweeper 审查:八轮迭代

ClawSweeper 是 OpenClaw 的专用代码审查机器人,使用 GPT-5.5 模型。

工作机制:

阶段说明
触发PR 创建、新 commit push、或 @clawsweeper re-review 评论
获取代码Fetch PR diff + 基于 main 分支生成 merge 结果
模型分析GPT-5.5 分析代码变更,检查 schema、类型、逻辑
发布结果原地编辑原有评论(不是新增评论)
标签同步自动添加 proof: sufficient 等标签

审查范围:

  • Schema 验证是否正确
  • 类型是否匹配(会运行类型检查)
  • SecretRef fallout 是否处理
  • 测试代码是否符合项目规范
  • 文档是否更新

Re-review 流程:

gh pr comment <number> --body "@clawsweeper re-review"

触发后,ClawSweeper 会:

  1. 接收 repository_dispatch 事件
  2. 重新运行完整审查
  3. 编辑原有的审查评论(保持评论 ID 不变)
  4. 更新标签状态

我们遇到的特殊情况:

ClawSweeper 的 re-review workflow 连续多次失败(状态为 Failed),原因是 ClawSweeper GitHub App 的 token 认证问题:

fatal: could not read Username for 'https://github.com': terminal prompts disabled

这是 ClawSweeper 自身的基础设施问题,不是 PR 代码问题。解决方法是更新 PR body 中的 commit SHA,触发自动 re-review,绕过有问题的 workflow。

审查分级:

  • P1: 严重问题(类型检查失败)
  • P2: 重要功能缺陷
  • P3: 建议性改进

审查迭代记录

轮次发现修复内容
1[P2] 需要验证 post-change config添加 schema validation 和 SecretRef fallout 检查
2[P1] 未使用变量 isSecretsPath移除未使用变量
3[P2] SecretRef fallout 检查不完整扩展到所有 secrets 相关路径
4[P2] 警告引用不存在的 flag添加 --allow-exec 支持
5[P3] 文档缺失更新 docs/cli/config.md
6[P2] 测试断言顺序不对先检查 __exit__:1,再检查错误消息
7[P2] 测试多余 setSnapshot移除 mode-error 测试中的未消费 mock
8[P2] 多余 .toString()parsedPath[2] 已经是 string,删除转换

第六轮详解:测试断言顺序

问题:测试代码中错误断言顺序不符合项目规范。

原理:exit() 不是真正退出,而是抛出哨兵异常

OpenClaw 的 CLI 测试框架使用 runtime.exit(code) 模拟进程退出。在测试环境中,exit() 不是真正退出进程,而是抛出一个特殊异常:

// src/cli/test-runtime-capture.ts
exit(code: number): never {
  throw `__exit__:${code}`;  // 抛出哨兵异常
}

当 CLI 命令遇到错误时,执行流程是:

命令逻辑 → runtime.error("错误消息") → runtime.exit(1) → 抛出 __exit__:1

修复前:

await expect(
  runConfigCommand(["config", "unset", "tools.alsoAllow", "--json"]),
).rejects.toThrow("config unset mode error: --json requires --dry-run.");

问题:toThrow("错误消息") 期望异常内容包含"错误消息",但实际抛出的是 __exit__:1,断言失败。

修复后:

await expect(
  runConfigCommand(["config", "unset", "tools.alsoAllow", "--json"]),
).rejects.toThrow("__exit__:1");
 
expect(mockWriteConfigFile).not.toHaveBeenCalled();
expectErrorIncludes("config unset mode error: --json requires --dry-run.");

错误消息通过 runtime.error() 写入 stderr,由测试框架捕获。必须先让 __exit__:1 异常被正确捕获,再检查 stderr 内容。


Real Behavior Proof 格式

为什么存在这个要求:

OpenClaw 是一个复杂的 AI Agent 系统,涉及:

  • 多进程架构(主进程 + worker 进程)
  • 外部工具执行(bash、文件操作、网络请求)
  • 多平台支持(Windows、Linux、macOS)

单元测试和 CI 检查只能验证代码逻辑,无法验证:

  • 真实环境中的进程通信是否正常
  • 文件系统操作是否正确
  • 网络 API 调用是否成功
  • 跨平台兼容性是否有效

External PR 的贡献者通常不在 OpenClaw 团队内部,无法访问内部测试环境。Real behavior proof 要求贡献者在自己的真实环境中运行代码,提供实际输出证明,确保功能确实可用。

这防止了什么问题:

问题类型单元测试能发现Real behavior proof 能发现
逻辑错误
类型错误
进程通信问题
文件路径问题(Windows vs Linux)
真实 API 调用失败
配置解析在真实环境失败

必需字段:

const requiredProofFields = [
  { key: "behavior", names: ["Behavior or issue addressed", ...] },
  { key: "environment", names: ["Real environment tested", ...] },
  { key: "steps", names: ["Exact steps or command run after this patch", ...] },
  { key: "evidence", names: ["Evidence after fix", ...] },
  { key: "observedResult", names: ["Observed result after fix", ...] },
  { key: "notTested", names: ["What was not tested", ...] },
];

正确格式:

## Real behavior proof
 
- **Behavior or issue addressed**: ...
- **Real environment tested**: Windows 10, Node.js v22.12.0, OpenClaw 2026.5.12-beta.1 (commit SHA)
- **Exact steps or command run after this patch**: ...
- **Evidence after fix**: ...
- **Observed result after fix**: ...
- **What was not tested**: ...

关键点:

  • 必须用 - **Key**: 格式,不是标题
  • Evidence 必须有真实终端输出(代码块)
  • commit SHA 必须与 PR head 一致

CI 检查责任边界

CI 检查的是 merge 后的完整代码库,不只是 PR 修改的文件。

判断方法:

# 本地检查修改的文件
pnpm test src/cli/config-cli.test.ts
 
# 如果自己文件相关测试通过,其他 CI 失败不是自己的问题

本 PR 遇到的情况:

检查项状态原因
check-lintfailmain 分支已有的 lint 错误
check-prod-typesfailmain 分支已有的类型错误
security-dependency-auditfail@mistralai/mistralai 供应链安全问题

结论:上述失败均与本次 PR 修改的文件无关。


标签系统

PR 标签来自不同系统:

标签来源
proof: suppliedCI 自动检查
proof: sufficientClawSweeper 标签同步
triage: needs-real-behavior-proof缺失 proof 时自动添加

当 ClawSweeper 说 "Sufficient" 但标签还没更新时,需要手动请求 re-review 同步。


修改的文件

文件修改内容
src/cli/config-cli.tsConfigUnsetOptions 类型、dry-run 逻辑、SecretRef fallout 检查、--allow-exec
src/cli/config-cli.test.ts4 个测试用例
docs/cli/config.mdconfig unset dry-run 文档

关键代码

SecretRef fallout 检查

const affectsSecretResolution =
  path === "secrets" ||
  path.startsWith("secrets.providers") ||
  path.startsWith("secrets.defaults");
 
if (affectsSecretResolution) {
  const refs = collectAffectedRefsForProviderRemoval(nextConfig, providerAlias);
  const { refsToResolve, skippedExecRefs } = selectDryRunRefsForResolution({
    refs,
    allowExecInDryRun: Boolean(cliOptions.allowExec),
  });
  // 验证 refs 是否可解析
}

模式验证

if (cliOptions.json && !cliOptions.dryRun) {
  throw new Error("config unset mode error: --json requires --dry-run.");
}
if (cliOptions.allowExec && !cliOptions.dryRun) {
  throw new Error("config unset mode error: --allow-exec requires --dry-run.");
}

最终状态

✅ Real behavior proof: pass
✅ ClawSweeper review: "needs maintainer review before merge"(最高评价)
✅ 所有测试通过(89 tests)
✅ 所有 P1/P2/P3 发现已修复

"needs maintainer review before merge" 表示 ClawSweeper 未发现更多问题,等待人工审核。


关键经验

1. ClawSweeper 是迭代式审查

每次修复后请求 re-review,会原地更新评论。

2. Real Behavior Proof 格式严格

必须用 - **Key**: 格式,必须有真实终端输出。

3. CI 失败可能不是你的问题

检查是否涉及你修改的文件,不涉及就不用处理。

4. 文档和代码同样重要

P3 级别的发现也要重视,文档更新是必需的。


相关链接


最后更新: 2026-05-14