首页
/ oh-my-openagent 的 work-with-pr 执行计划:以 atlas 钩子 worktree_path 崩溃修复为例,拆解 Agent 全生命周期 PR 工作流

oh-my-openagent 的 work-with-pr 执行计划:以 atlas 钩子 worktree_path 崩溃修复为例,拆解 Agent 全生命周期 PR 工作流

2026-09-04 22:32:50作者:范靓好Udolf

本文以 oh-my-openagent 仓库中由 work-with-pr 技能生成的一份真实执行计划 execution-plan.md 为主体,完整还原“从隔离 worktree 建立、根因修复、测试覆盖、本地验证,到原子提交、PR 创建、多门禁验证循环、合并与清理”的端到端 PR 交付流程,并结合当前仓库源码(boulder-state 包与 omo-opencode 的 atlas 钩子)说明该计划针对的缺陷为何成立、以及计划中各步骤与仓库实际实现之间的对应关系。读完本文,你可以掌握一种把单点缺陷修复组织为可审计、可回滚、门禁驱动的 Agent 交付方案的方法,并理解 readBoulderState() 这类 JSON 状态读取函数中的运行时类型隐患。

一、这份执行计划是什么

execution-plan.md 是一份具体的任务执行计划,标题为 “Execution Plan — Fix atlas hook crash on missing worktree_path”,即“修复 atlas 钩子在缺少 worktree_path 字段时崩溃”的完整实施蓝图。它存放在技能评估工作区 work-with-pr-workspaceiteration-1/eval-2/with_skill/outputs/ 目录下,与 code-changes.mdpr-description.mdverification-strategy.md 等产出物并列,共同构成一次 work-with-pr 技能评估运行的完整记录(同级目录还有 without_skill/ 对照组、grading.jsontiming.json)。

该计划是 work-with-pr 技能定义 的一次具体实例化:技能文档定义了 Phase 0~4 的通用生命周期(Setup → Implement → PR Creation → Verify Loop → Merge),而这份执行计划则把它落到一个具体缺陷上——atlas 会话空闲钩子(session.idle)在处理 boulder.json 状态文件时,一旦 worktree_path 字段缺失或为 null 就可能崩溃。

二、缺陷背景:readBoulderState() 的运行时类型隐患

计划将根因定位在 readBoulderState():它对 boulder.json 执行原始 JSON.parse 后直接 as BoulderState 类型断言,运行时类型违约因此被放行。这一点可以与当前仓库源码相互印证:

  • 当前实现位于 read-state.ts,函数签名为 readBoulderState(directory: string): BoulderState | null。其核心逻辑是:
const content = readFileSync(filePath, "utf-8")
const parsed = JSON.parse(content)
// 仅校验 parsed 是非空对象,随后:
normalizeState(parsed)
const state = parsed as BoulderState   // 直接断言,字段级校验有限

可以看到,normalizeState() 只规范化了 session_idssession_originstask_sessionsworks 等会话类字段(见 read-state.ts),对 worktree_path 这一字段并没有做 string | undefined 的强制约束。

  • 而类型契约明确要求可选字符串:在 types.ts 中,BoulderStateBoulderWorkState 等结构体均以 worktree_path?: string 声明该字段(types.ts)。也就是说,契约允许“不存在”(undefined),但下游代码若假定“存在即字符串”,null 或其他类型就会穿透 as BoulderState 断言流入后续逻辑

  • 下游消费方是 atlas 钩子。idle-event.ts 中的 handleAtlasSessionIdle() 在会话空闲时解析活跃 boulder 会话并驱动续跑逻辑;idle-continuation.ts 则直接读取 currentBoulder.worktree_path 传入续跑上下文。一旦该值在运行时是 null,注入续跑提示词时就会出现计划中所说的 [Worktree: null] 之类的脏数据乃至崩溃路径。

路径映射说明:执行计划成文时使用的相对路径为 src/features/boulder-state/storage.tssrc/hooks/atlas/idle-event.ts;从当前源码结构看,仓库已进行 monorepo 化重组,同一逻辑现分别位于 packages/boulder-state/src/storage/read-state.tspackages/omo-opencode/src/hooks/atlas/idle-event.ts(后者仍保留 idle-event.ts 文件名与 scheduleRetry 调用链,idle-event.ts)。理解这一映射关系,才能把计划中的修复步骤落到当前代码上。

三、Phase 0:建立隔离 worktree

计划的第一步不是写代码,而是建立隔离的执行环境:

# 1. 从 origin/dev 建立 worktree
git fetch origin dev
git worktree add ../omo-wt/fix-atlas-worktree-path-crash origin/dev

# 2. 在 worktree 内创建特性分支
cd ../omo-wt/fix-atlas-worktree-path-crash
git checkout -b fix/atlas-worktree-path-crash

这里的两个细节与 work-with-pr 技能定义 的设计原则完全一致:

  1. worktree 放在仓库的同级目录(../omo-wt/)而非仓库内部——避免 git 嵌套仓库问题,同时保证用户主工作目录中的未提交改动不受污染;
  2. “一 PR 一 worktree”的隔离模型——主工作目录被视为只读上下文,分支切换可能摧毁其中的在途工作;隔离也使多个独立 PR 可以并行构建互不干扰。

值得注意的是,计划从 origin/dev 而非本地分支建立基线,这与技能中 BASE_BRANCH="dev" 且 “CI blocks PRs to master” 的约束呼应:所有 PR 一律以 dev 为基。

四、Phase 1:五步实施(实现、防护、测试、验证、提交)

这是计划的主体,共五步,层次清晰:先修根因,再补防御,然后补测试,接着本地验证,最后原子提交

Step 1:在 readBoulderState() 中净化 worktree_path

  • 在 JSON 解析之后对 worktree_path 做净化(sanitize);
  • 确保 worktree_path 的运行时形态只能是 string | undefined,绝不接受 null 或其他类型;
  • 计划明确指出这是根因修复:原始 JSON.parse + as BoulderState 断言允许类型违约在运行时存活。

对照当前 read-state.ts 的实现,这正是 normalizeState() 应当承担、但当前尚未覆盖 worktree_path 的字段级规范化——把“拒绝非字符串值”的逻辑放入这里,与现有 normalizeSessionFields()normalizeWorkSessionFields() 的写法保持一致,是最贴合当前代码风格的落点。

Step 2:在 idle-event.ts 中加防御性守卫

  • 在把 boulderState.worktree_path 交给 injectContinuation 之前,先验证它是字符串;
  • scheduleRetry 回调中施加同样的守卫;
  • 目标是形成纵深防御:即使 readBoulderState 被绕过(例如状态来自其他写入路径),空闲事件处理器也不会崩溃。

这与当前 idle-event.ts 的结构吻合:handleAtlasSessionIdle() 在多个前置条件(后台任务运行中、续跑冷却期、停滞检测等)不满足时调用 scheduleRetry({ ctx, sessionID, sessionState, options })idle-event.ts)——守卫需要同时覆盖“立即续跑”和“延迟重试”两条路径,这正是计划强调 scheduleRetry 回调的原因。

Step 3:测试覆盖(given/when/then 风格)

计划要求新增三类测试,且遵循既有测试模式:

测试场景 断言目标
boulder.json不存在 worktree_path 字段 session.idle 正常处理,不崩溃
boulder.jsonworktree_path: null session.idle 正常工作,且提示词中不出现 [Worktree: null]
readBoulderState 的净化行为 nullworktree_path 归一化为 undefined

当前仓库中 atlas 钩子的测试组织方式印证了这种“按事件切片”的模式:idle-continuation.test.tsboulder-continuation-injector.test.tsindex.test.ts 等测试文件与各自的实现文件一一对应;boulder-state 包侧则有 read-state.test.ts 专门覆盖 readBoulderState 的解析与归一化行为。计划中指定的 src/hooks/atlas/index.test.ts 在当前布局下对应 index.test.ts,字段净化断言则自然落在 read-state.test.ts 的领域内。

Step 4:本地验证(与 CI 同构的前置检查)

bun run typecheck
bun test src/hooks/atlas/
bun test src/features/boulder-state/
bun run build

这四条命令复刻了 CI 将执行的检查,并按受影响模块收窄测试范围(只跑 src/hooks/atlas/src/features/boulder-state/ 两个相关目录)。技能定义中将其定位为“约 3–5 分钟 CI 往返的廉价预过滤”,而非验证手段本身——它的作用是避免把明显失败推上远端。

Step 5:原子提交

git add src/features/boulder-state/storage.ts src/hooks/atlas/idle-event.ts src/hooks/atlas/index.test.ts
git commit -m "fix(atlas): prevent crash when boulder.json missing worktree_path field

readBoulderState() performs unsafe cast of parsed JSON as BoulderState.
When worktree_path is absent or null in boulder.json, downstream code
in idle-event.ts could receive null where string|undefined is expected.

- Sanitize worktree_path in readBoulderState (reject non-string values)
- Add defensive typeof check in idle-event before passing to continuation
- Add test coverage for missing and null worktree_path scenarios"

这条提交信息本身就是可复用范本:标题行一句话概括修复,正文解释根因(不安全断言 + 字段可为 null),再用 bullet 列出三个变更面。3 个文件对应“根因修复 + 防御守卫 + 测试”三条 bullet,符合技能中“每个提交配对实现与测试”的原子提交策略——CI 失败时可单独隔离一个逻辑单元而不必整体回退。

五、Phase 2:PR 创建

git push -u origin fix/atlas-worktree-path-crash
gh pr create \
  --base dev \
  --title "fix(atlas): prevent crash when boulder.json missing worktree_path" \
  --body-file /tmp/pull-request-atlas-worktree-fix.md

三个要点:

  • --base dev 与 Phase 0 的基线选择保持一致;
  • PR 正文通过 --body-file 从预写的文件载入(对应同目录产出物 pr-description.md),避免在命令内嵌大段文本;
  • 技能定义要求 PR 正文面向未跟进实现过程的英文审阅者,按审阅相关区域(而非文件)组织变更,并让 QA 证据可审计——“测试全绿”不构成完成判据,到达 OpenCode/Codex 运行面的改动必须有落到磁盘的实机证据。

六、Phase 3:无上限的验证循环(三道门禁)

- Gate A (CI):gh pr checks --watch — 等待所有检查变绿
- Gate B (review-work):运行 5 智能体评审(Oracle goal、Oracle quality、Oracle security、QA execution、context mining)
- Gate C (Cubic):等待 cubic-dev-ai[bot] 回复 "No issues found"
- 任一门禁失败:fix-commit-push,重新进入验证循环

这体现了 work-with-pr 技能的核心机制——循环没有迭代上限,任何失败门禁都会把执行路由回 Phase 1 的修复轨道,修复须遵守同样的范围纪律(只修门禁指出的问题、行为变化则补新 QA 证据、原子提交、重新从门禁 A 开始全量验证)。这份执行计划相比技能通用定义还多了一道 Gate B(5 智能体评审),说明门禁组合可按任务风险裁剪,但“门禁失败即回炉、不得绕过”的不变量保持不变。

七、Phase 4:合并与清理

gh pr merge --squash --delete-branch
git worktree remove ../omo-wt/fix-atlas-worktree-path-crash

合并完成后立即移除 worktree,防止磁盘膨胀;技能定义还要求在删除前把 worktree 内生成的 .omo/ 状态(任务状态、计划、notepad)同步回主仓库,因为 .omo/ 通常被 gitignore,其中的文件不会随合并带回。需要说明的差异点:当前 SKILL.md 明确写着“本仓库要求 merge commit,永不使用 --squash--rebase”,而这份较早的执行计划使用的是 --squash——从文档演进看,合并策略在技能迭代中被收紧为 gh pr merge --merge 路线,执行计划反映的是当时的版本约定。

八、这份执行计划的可迁移价值

execution-plan.md 放回 work-with-pr 技能 的坐标系里看,它展示了这套工作流的几个关键工程决策:

  1. 隔离先行:任何实现都发生在任务专属 worktree 中,主工作目录只作为只读上下文,为并行多 PR 与失败恢复留出了空间(技能规定失败时不得删除 worktree,保留人工接管入口);
  2. 根因与防御分层:Step 1 修状态读取层的根因,Step 2 在消费层补守卫,两层互为冗余,单点失效不致崩溃;
  3. 测试与实现一一对应:三个测试场景分别锁定“缺失”“null”“归一化”三种运行时形态,与根因分析中的类型违约面精确对齐;
  4. 门禁驱动的收敛:CI、多智能体评审、外部 bot(Cubic)构成多源交叉验证,失败即回炉的无上限循环保证“交付”等价于“所有活动门禁同时通过”。

如果你在当前仓库中遇到类似的 JSON 状态文件类型违约问题,这份计划给出的排查路径依然有效:先定位 JSON.parse + as X 断言点(参见 read-state.ts 的处理方式),再沿下游消费链(参见 idle-event.tsidle-continuation.ts)确认违约值如何扩散,最后按“净化根因 + 消费端守卫 + 三场景测试 + 本地同构验证 + 原子提交”的顺序组织交付。

九、相关文件索引

角色 路径
本文主体文档 execution-plan.md
技能定义(通用生命周期) work-with-pr/SKILL.md
配套产出物 code-changes.mdpr-description.mdverification-strategy.md
状态读取实现(根因所在) packages/boulder-state/src/storage/read-state.ts
状态类型契约 packages/boulder-state/src/types.ts
atlas 空闲事件处理 packages/omo-opencode/src/hooks/atlas/idle-event.ts
续跑注入(worktree_path 消费端) packages/omo-opencode/src/hooks/atlas/idle-continuation.ts
相关测试 index.test.tsread-state.test.ts
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384