功能概述
本 PR 为 openclaw config unset 命令添加了三个标志:
| 标志 | 功能 |
|---|---|
--dry-run | 预览删除结果,不实际修改配置文件 |
--json | 结构化输出,便于脚本解析(需配合 --dry-run) |
--allow-exec | 在 dry-run 模式下允许执行命令验证 SecretRef |
这些功能使 config unset 与兄弟命令 config set 和 config 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-provider,channels.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 会:
- 接收
repository_dispatch事件 - 重新运行完整审查
- 编辑原有的审查评论(保持评论 ID 不变)
- 更新标签状态
我们遇到的特殊情况:
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-lint | fail | main 分支已有的 lint 错误 |
check-prod-types | fail | main 分支已有的类型错误 |
security-dependency-audit | fail | @mistralai/mistralai 供应链安全问题 |
结论:上述失败均与本次 PR 修改的文件无关。
标签系统
PR 标签来自不同系统:
| 标签 | 来源 |
|---|---|
proof: supplied | CI 自动检查 |
proof: sufficient | ClawSweeper 标签同步 |
triage: needs-real-behavior-proof | 缺失 proof 时自动添加 |
当 ClawSweeper 说 "Sufficient" 但标签还没更新时,需要手动请求 re-review 同步。
修改的文件
| 文件 | 修改内容 |
|---|---|
src/cli/config-cli.ts | ConfigUnsetOptions 类型、dry-run 逻辑、SecretRef fallout 检查、--allow-exec |
src/cli/config-cli.test.ts | 4 个测试用例 |
docs/cli/config.md | config 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