📦 Wip — WIP(进行中的工作)
v1.9.82Branch Guard 通过拦截非法写入和破坏性命令来强制执行分支纪律,首次写入前必须阅读入门文档,跟踪重试次数……
详细分析 ▾
运行时依赖
版本
# wip-branch-guard v1.9.82 修复跨会话状态冲突,移除 env-var 逃生舱,阻止 Layer 3 对临时目录写入触发 关闭 #253 ## 额外修复:Layer 3 的 onboarding/阻塞文件闸门跳过临时目录与共享状态 Bash 写入 Layer 3 的 onboarding 与阻塞文件追踪闸门,会对任何提取出的写入目标列表非空的 Bash 命令触发,包括写入 `/tmp`、`/var/tmp`、`/var/folders/.../T/`。临时目录白名单仅存在于 `ALLOWED_BASH_PATTERNS` 且仅作用于 Layer 1(主分支写入拦截);Layer 3 先执行并拒绝,给出 onboarding 提示,尽管 `/tmp` 不在任何 git 仓库内。 症状:在新会话仓库主分支执行 `cp source /tmp/x` 被拦截并提示“需要 onboarding”。 2026-04-21 的 Phase 12 审计测试在 `test.sh` 中首次暴露;8 个临时目录测试用例(`cp/mv/rm/mkdir/touch/>/tee` 到 `/tmp` 与 `cp` 到 `/var/tmp`)全部失败。 修复:在 Layer 3 调用前过滤 `writeTargets`,排除临时路径与共享状态路径,与 Layer 1 白名单对称,并与 Edit/Write 工具已有的 `isSharedState` 跳过一致。`extractWriteTargets` 本身不变;过滤位于 Layer 3 调用处,保持函数通用。 ## 该 bug 机器上每个 Claude Code 会话都写入同一文件 `~/.ldm/state/guard-session.json`。guard 的 `detectNewSession()` 在 `session_id` 不匹配时触发,会清空整个状态文件(`onboarded_repos_canonical`、`read_files`、`recent_denials`)再写入自身状态。 Parker 默认同时运行多个 CC 会话,任一会话的工具调用会覆盖其他会话的 onboarding 与读取追踪状态。 症状:代理读取仓库 `README.md` + `CLAUDE.md`,首次写入成功,随后同一工作区的下一次 Write 被 guard 要求重新读取相同文档。2026-04-21 的 dogfood 测试中,代理在同一会话内重复读取 onboarding 文档三次仍被拦截。 根因:一台机器共用一份状态文件。 ## 修复 ### 每会话独立状态文件 状态现存放于 `~/.ldm/state/guard-session-<session_id>.json`。每个 CC 会话独占文件,跨会话乒乓从源头消除:不同会话写不同文件,互不覆盖。 `statePathFor()` 内的净化器将 `session_id` 映射为安全文件名段(字母数字、短横线、下划线;最多 64 字符),防止异常 session ID 逃逸状态目录。 ### 同会话内基于锁文件的原子写 同一会话并行工具调用(如一次助手回合触发四个 Read)也在状态文件上竞争读写。`writeSessionState()` 现通过 `openSync(..., 'wx')` 获取锁文件 (`guard-session-<sid>.json.lock`),2 秒获取预算,10 秒过期回收。锁失败退化为尽力写:每会话文件修复是主承重,锁为双保险。 ### TTL 清理 每会话文件随时间累积。guard 每次调用运行 `cleanupStaleStateFiles()`,删除 24 小时前的 `guard-session-*.json` 或 `.lock`。小状态目录的 `readdirSync` 耗时亚毫秒,扫描成本可忽略。 ### 已移除:`LDM_GUARD_SKIP_ONBOARDING` 与 `LDM_GUARD_ACK_BLOCKED_FILE` 这两个 env var 原为上述状态 bug 的逃生舱。bug 已根修,逃生舱只会训练代理绕过 guard:每次“请设此 env var 解堵”都是设计内的 workaround 生效。现两变量被忽略。 唯一保留的覆盖 `LDM_GUARD_UPSTREAM_PR_APPROVED` 为合法人工授权(Parker 批准向上游仓库提 PR),非 guard bug 绕过。 onboarding 与阻塞文件重试闸门的拒绝信息不再提示设置这些 env var。 `approvalCheck` 审计项 `skip-onboarding-approved` 与 `ack-blocked-file-approved` 已移除(不再触发)。 ## diff 内容 - `tools/wip-branch-guard/guard.mjs` 新增:`statePathFor()`、`withStateLock()`、`cleanupStaleStateFiles()`、每会话状态常量 变更:`readSessionState()` 接收 `sessionId`;`writeSessionState()` 经锁文件写往每会话路径 移除:`main()` 中 `detectNewSession` 清空逻辑(每会话文件使其过时);移除 env var 的 `approvalCheck` 调用与审计项;拒绝信息中的逃生舱提示 - `tools/wip-branch-guard/test.sh` 翻转:`LDM_GUARD_SKIP_ONBOARDING` / `LDM_GUARD_ACK_BLOCKED_FILE` 测试现断言 env var 被忽略(预期拒绝) 新增:“跨会话状态隔离”回归测试块(6 用例 + 2 磁盘存在断言)复现乒乓场景 - `tools/wip-branch-guard/SKILL.md` 文档更新:每会话状态结构、版本历史 v1.9.82 条目、覆盖表仅列剩余 env var、Layer 3 章节移除 env var 说明 - `tools/wip-branch-guard/package.json` 版本号 1.9.81 → 1.9.82 ## 测试计划 ```bash tools/wip-branch-guard/test.sh ``` 预期:95 通过,0 失败,8 跳过(仅当测试运行器自身 CWD 位于 main 时运行的 on-main-branch 用例)。 关键回归用例: - `iso: session A still onboarded after B's activity`(精确复现 bug) - `iso: per-session file for A exists on disk` - `onboarding: LDM_GUARD_SKIP_ONBOARDING=1 IGNORED (still denies)` ## 迁移 旧文件 `~/.ldm/state/guard-session.json` 安装后成孤儿。v1.9.82 不再读写,可安全删除: ```bash rm -f ~/.ldm/state/guard-session.json ``` `cleanupStaleStateFiles()` 不处理它(正则匹配 `guard-session-*.json`),留也无害,直到手动清理。 ## 接受的权衡 移除 env-var 逃生舱后,若 guard 再出故障,代理将真被卡死:唯一出路是打补丁重装。我们接受,因为保留逃生舱会维持 workaround 循环。补偿路径为 SKILL.md 记录的“安装即逃生舱”(回滚到旧标签 `ldm install /tmp/toolbox-old`)及 bypass 审计日志,后者记录每次拒绝及上下文,可在分钟级而非小时级定位下一状态 bug。 ## 本 PR 未处理(仍开启) 修复审计发现三项缺口,此处未修,已另开 issue,供下次 guard PR 拾取: - 缺口 A:无主动 SessionStart 扫描。onboarding 闸门为被动(首次写入触发,非会话开始或首次 cd)。 - 缺口 B:shell 重定向漏洞。guard 拦截 Edit/Write 与 `python -c` / `node -e`,但未对 Bash `>`、`>>`、`tee` 向受保护路径(非 `.worktrees/`、`/tmp` 等)做模式匹配。 - 缺口 C:被动绕过审计。`~/.ldm/state/bypass-audit.jsonl` 记录拒绝与(本 PR 前)env-var 覆盖,但会话起止未解析该文件向 Parker 提示重复绕过。 ## 协作者 Parker Todd Brooks、Lēsa(oc-lesa-mini,Opus 4.7)、Claude Code(cc-mini,Opus 4.7)。
安装命令
点击复制技能文档
|---| | git 仓库 main 分支 | 拒绝 | | 功能分支,未在关联 worktree | 拒绝 | | 功能分支,已在关联 worktree | 允许 | | 共享状态路径(见下) | 始终允许 | | 不在任何 git 仓库 | 允许 |
共享状态路径(始终允许):
~/.claude/plans/、~/.claude/projects//memory/、~/.claude/rules/、~/.openclaw/workspace/、~/.openclaw/extensions/、~/.ldm/shared/、~/.ldm/messages/、~/.ldm/templates/、~/.ldm/extensions/、~/.ldm/logs/、~/.ldm/agents//memory/daily/.md、~/.ldm/memory/shared-log.jsonl、~/.ldm/memory/daily/.md、workspace/SHARED-CONTEXT.md、workspace/TOOLS.md、workspace/MEMORY.md、workspace/IDENTITY.md、workspace/SOUL.md、workspace/WHERE-TO-WRITE.md、workspace/HEARTBEAT.md、workspace/memory/.md、CLAUDE.md。
worktree 约定:.worktrees/--/。
引导复合命令(git worktree add .worktrees/... && mkdir -p .../ai/... && cp src dest)显式允许。
第二层 … 破坏性命令拦截(任意分支)
无论分支,一律拒绝:git clean -f(删除未跟踪文件)git checkout --(还原文件)git checkout .(还原全部)git stash drop/pop/clear(销毁暂存)git reset --hard(清除未提交更改)git restore(还原文件;--staged安全且允许)python -c "open().write()"/node -e "writeFile()"(脚本语言绕过写入)--no-verify(跳过 git hooks)- 无
--force-with-lease的git push --force
引号内容在匹配前被剥离,因此 gh issue create --body 'use git checkout -- to fix' 被允许。
第三层 … 会话级门控(1.9.77 新增)
1. 首次写入前 onboarding
会话中对任何 git 仓库的首次写入前,守卫要求代理已用 Read 工具读取仓库根目录指定 onboarding 文档。 检查自动从 Read 工具调用(Read|Glob|Write|Edit|NotebookEdit|Bash)中捕获。
必须读取:README.md、CLAUDE.md 及根目录下匹配 RUNBOOK.md、LANDMINES.md、WORKFLOW*.md 的文件。
TTL:onboarding 后 2 小时活动有效,新会话或超时失效。 任何仓库的入门仪式:
``
git rev-parse --show-toplevel # 确认仓库- Read README.md # 用 Read 工具,非 cat
- Read CLAUDE.md # 若存在
- Read RUNBOOK / LANDMINES / WORKFLOW # 若存在于根目录
- 继续 Write/Edit
无覆盖。 v1.9.82 前可用 LDM_GUARD_SKIP_ONBOARDING 绕过,已移除;真正阅读文档是唯一途径。 2. 最近拦截文件追踪
每次拦截对某文件的写入时,记录 {ts, path, tool, command_stripped} 至会话最近拒绝队列(最近 20 条,1 小时窗口)。
若后续写文件工具再次命中同一路径,守卫再次拒绝并附上前次拦截上下文。
捕获模式:Edit X → 被拒 → 代理用 cat > X 重试,第二次仍被拦截为“等效绕过”。
无覆盖。 v1.9.82 前可用 LDM_GUARD_ACK_BLOCKED_FILE 承认并继续,已移除;若拦截有误,修正根因而非绕过。 3. 外部 PR 创建门控(1.9.80 新增)
拒绝:
gh pr create --repo /gh pr create --repo / --head :(跨 fork)当 cwd 的 git origin 为/时的gh pr create [--web]gh api repos///pulls ... -X POST
允许:
所有者为wipcomputer/的上述命令对任意仓库的gh pr view、gh pr list、gh pr merge、gh pr edit(只读/交互,不创建)gh api repos///issues ... -X POST(创建 issue,非 PR)
触发事件:2026-04-18 PR #89 事件,代理未经批准直接向 steipete/imsg 提 PR。
覆盖:LDM_GUARD_UPSTREAM_PR_APPROVED=/`(目标