
HelloAgents Code Agent CLI 补丁应用失败排查从 Patch must start with *** Begin Patch 读懂 Codex 风格补丁解析机制【免费下载链接】hello-agents 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents导读本文以 HelloAgents Code Agent CLI 项目Co-creation-projects/YYHDBL-HelloCodeAgentCli中一张真实的阻塞型blocker笔记note_20251219_150917_17.md为切入点完整还原补丁应用失败这一高频问题的产生链路CLI 如何提取模型回复中的补丁、执行器如何解析 Codex 风格的*** Begin Patch ... *** End Patch格式、以及格式不合法时为何会抛出 Patch must start with *** Begin Patch。读完本文你将掌握该 CLI 的补丁格式规范、解析器的宽容与严格边界、失败笔记的落盘机制并获得一份可直接复用的补丁排查清单。一次真实的补丁失败blocker 笔记全文解读在仓库Co-creation-projects/YYHDBL-HelloCodeAgentCli/.helloagents/notes/note_20251219_150917_17.md中记录了一条由 Agent 自动生成的结构化笔记完整内容如下--- id: note_20251219_150917_17 title: Patch failed type: blocker tags: [hello_agents_forStudy, patch_failed] created_at: 2025-12-19T15:09:17.654597 updated_at: 2025-12-19T15:09:17.654601 --- # Patch failed Error: Patch must start with *** Begin Patch User input: 将testDemo文件夹下的html文件内容改成中 文的 并且 用js实现一些简易的效果 让页面开起来更生 动 Patch: text *** Begin Patch ... *** End Patch这条笔记暴露了两个关键信息 1. **用户任务**把 testDemo 文件夹下的 HTML 文件内容改成中文并用 JS 实现一些简易效果让页面更生动——这是一个典型的前端页面改造需求与 code_agent/prompts/system.md 中补丁正确示例testDemo/style.css和 code_agent/prompts/react.md 中的示例testDemo/hello.html高度吻合。 2. **失败原因**解析器抛出了 Patch must start with *** Begin Patch。而笔记中记录的 Patch 字段显示模型产出的补丁被压缩成了形如 *** Begin Patch ... *** End Patch 的单行文本既不是独立的 *** Begin Patch 开头也没有独立成行的 *** End Patch 结尾。 这条笔记本身不是答案而是一份需要结合源码才能彻底读懂的**故障快照**。下面我们从解析器与 CLI 的源码出发还原它为何发生、以及如何避免。 ## 补丁格式规范Codex 风格三操作 HelloAgents Code Agent CLI 采用与 Claude Code / Codex 一致的补丁协议。在 [system.md](https://link.gitcode.com/i/b10b986b7d5dcd4fa7d75022d8115302) 中系统提示词对补丁格式给出了完整定义 text *** Begin Patch *** Add File: path/to/new_file.py 文件内容... 可以多行... *** Update File: path/to/existing_file.py 更新后的完整文件内容... *** Delete File: path/to/old_file.py *** End Patch关键规则来自 system.md规则说明第一行必须是*** Begin Patch前面不能有任何文字最后一行必须是*** End Patch操作行格式*** Add File: path/*** Update File: path/*** Delete File: path内容Add / Update 后面跟完整文件内容Delete 后面不需要内容包裹不要在补丁外包裹 markdown 代码块不要用 路径相对于仓库根目录system.md 同时给出了错误与正确的对照示例。错误示例*** Begin Patch前面带了这是一个补丁这样的文字会被直接判为非法。正确示例第一行就是*** Begin Patch其后紧跟*** Add File: testDemo/style.css和文件正文。在 react.md 中规则被进一步细化并配了更完整的示例Finish[ 已为 testDemo/hello.html 添加样式。 *** Begin Patch *** Update File: testDemo/hello.html !DOCTYPE html html head style body {{ background: #f0f0f0; }} /style /head body h1Hello World/h1 /body /html *** End Patch ]react.md 特别强调三条易错点说明文字和补丁之间要有空行分隔*** Begin Patch独占一行前面不能加任何字符冒号、文字都不行不要用 markdown 代码块包裹补丁。对照这组规范回看失败笔记模型产出的补丁是*** Begin Patch ... *** End Patch——三个要素全部违反Begin 标记前有内容反引号、Begin/End 未独立成行、补丁被压缩为单行。这正是解析器拒绝执行的直接原因。解析器源码级剖析_parse_patch 的宽容与严格补丁的解析与执行全部集中在 apply_patch_executor.py 的ApplyPatchExecutor._parse_patch方法约 L262-L341。理解这段代码就能精确回答什么情况下会报 Patch must start with *** Begin Patch。第一步跳过围栏宽容处理前置噪声# 宽容处理跳过前置空行/代码块围栏找到真正的开头 while lines and lines[0].strip() in {, , patch, diff, text}: lines lines[1:]解析器并非绝对苛刻——它允许补丁前存在空行和、patch、diff、text等代码块围栏会自动跳过。这说明用围栏包裹补丁在 CLI 侧的_extract_patch之后通常已被处理但执行器仍做了二次容错。第二步定位 Begin 标记否则直接报错# 如果仍未以标头开头尝试向下寻找标头并截取 if lines and lines[0].strip() ! *** Begin Patch: for idx, l in enumerate(lines): if l.strip() *** Begin Patch: lines lines[idx:] break if not lines or lines[0].strip() ! *** Begin Patch: raise PatchApplyError(Patch must start with *** Begin Patch)注意这里的关键逻辑strip()后必须与字符串*** Begin Patch完全相等。只要第一行被追加了任何内容——比如反引号*** Begin Patch、冒号、说明文字——strip()后就不会等于*** Begin Patch。随后解析器会尝试向下寻找独立成行的*** Begin Patch若整篇文本中都不存在完全匹配的行就抛出本次笔记中记录的错误。从笔记内容可以推断模型把整段补丁压缩成单行*** Begin Patch ... *** End Patch其中 Begin 标记带反引号后缀、End 标记与前文挤在同一行导致精确匹配两处全部落空最终在进入任何文件操作之前就被拒绝。这是格式层校验失败并非文件写入失败——补丁从未被执行。第三步校验 End 标记while lines and lines[-1].strip() in {, }: lines lines[:-1] if not lines or lines[-1].strip() ! *** End Patch: for idx in range(len(lines) - 1, -1, -1): if lines[idx].strip() *** End Patch: lines lines[: idx 1] break if not lines or lines[-1].strip() ! *** End Patch: raise PatchApplyError(Patch must end with *** End Patch)同样的精确匹配策略也作用于结尾允许尾部空行与围栏但最后必须有独立成行的*** End Patch否则抛出Patch must end with *** End Patch。这一对错误信息Begin / End构成了补丁格式最外层的两道校验闸门。第四步逐行解析操作通过头尾校验后解析器按行扫描操作指令L305-L339*** Add File: path收集后续行作为新文件内容兼容两种形式——规范形式每行以开头和宽松形式直接给出正文模型可能省略*** Delete File: path无需内容*** Update File: path收集后续行作为更新载荷其余非空行若不匹配任何操作头抛出Unexpected patch line。第五步Update 载荷的 hunk 应用Update 操作通过_apply_update_payload→_split_hunks→_apply_hunk实现L369-L494将载荷按分隔符或空行切分为多个 hunk每个 hunk 中的 上下文、-删除行、新增行被拆分为 before/after 两个块然后在原文件中做精确子序列匹配。若找不到匹配上下文抛出Patch hunk context not found; file changed?并附带rel_path:search:上下文前80字符的重新检查提示随后_apply_update_payload会尝试宽松兜底——把 hunk 的 after 部分直接拼成新文件内容_hunks_to_after。此外若 Update 载荷中没有任何/-/ 空格前缀行则被判定为整文件替换L374-L377直接返回原文——这是为了兼容模型偶尔直接给出完整新文件内容的场景。CLI 侧的提取、规范化与确认补丁进入执行器前还要过三关在 hello_code_cli.py 中模型回复到执行器之间还有一道完整的前置管线第一关提取_extract_patchL32-L43PATCH_RE re.compile(r\s*\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch, re.MULTILINE) PATCH_FENCE_RE re.compile( r(?:patch|diff|text)?\s*(\*\*\* Begin Patch[\s\S]*?\*\*\* End Patch)\s*, re.MULTILINE, )先尝试从patch/diff/text围栏中提取补丁主体失败则退回宽松的正则匹配允许前导空白。注意PATCH_RE 要求文本中真实存在*** Begin Patch ... *** End Patch的字面序列。如果模型像失败笔记中那样把标记写坏如*** Begin Patch ... *** End Patch单行、带反引号正则无法命中_extract_patch返回None根本走不到执行器——此时用户只会看到模型回复但不会产生任何落盘。这也解释了为什么失败笔记中记录的补丁看起来还在它是note_tool记录的原始内容不代表执行器实际收到过合法补丁。第二关规范化_normalize_patchL46-L60if stripped.startswith((Add File:, Update File:, Delete File:)) and not stripped.startswith(*** ): out.append(*** stripped)宽容处理模型遗漏***前缀的情况将其补全为标准 Codex 风格。第三关空补丁忽略与高风险确认if patch_text.strip() *** Begin Patch\n*** End Patch: continue空补丁没有任何操作会被静默忽略。随后_patch_requires_confirmationL63-L81按三条策略判定是否需要人工确认触发条件阈值包含*** Delete File:操作任意删除即确认文件操作数量≥ 6 个变更行数/-开头行≥ 400 行命中任一条件时CLI 会打印⚠️ 检测到高风险补丁删除/大规模变更。是否应用(y/n)等待用户输入y/yes才继续输入其他值则取消应用。失败如何被记录NoteTool 的 blocker 笔记机制笔记不会凭空出现。在 hello_code_cli.py 中补丁应用抛出的PatchApplyError被显式捕获并落盘为笔记except PatchApplyError as e: print(\n c(f❌ Patch failed: {e}, ERROR)) agent.note_tool.run({ action: create, title: Patch failed, content: fError: {e}\n\nUser input:\n{user_in}\n\nPatch:\n\ntext\n{patch_text}\n\n, note_type: blocker, tags: [project, patch_failed], })title固定为Patch failednote_type为blocker阻塞项tags包含项目名与patch_failedcontent记录错误消息、用户原始输入和补丁原文。这正是本笔记note_20251219_150917_17.md的生成路径。与之对称补丁成功时也会写入Patch applied笔记L196-L204note_type为actiontags 含patch_applied。笔记的存储格式由 note_tool.py 定义Markdown 文件 YAML 前置元数据id/title/type/tags/created_at/updated_at索引保存在notes_index.json。支持create / read / update / delete / list / search / summary七种操作笔记类型包括task_state、conclusion、blocker、action、reference、general。默认工作区为repo/.helloagents/notes/与本次笔记所在目录完全一致。这套机制的工程价值在于失败即沉淀。blocker 笔记把错误消息、用户输入、原始补丁三者绑定保存后续无论是人工复盘还是 Agent 通过context_fetch/note[search]检索都能完整还原失败现场形成可检索的排错知识库。格式校验之外执行器的安全纵深为什么解析器宁可报错也不硬着头皮应用因为ApplyPatchExecutor的设计目标首先是安全。在 apply_patch_executor.py 的类注释与实现中可以看到完整的防护链安全机制实现位置说明路径逃逸防护_safe_pathL185-L207拒绝绝对路径与~开头路径resolve()后校验前缀必须位于 repo_root 内拒绝修改符号链接后缀白名单_enforce_suffixL209-L221默认仅允许.py .md .toml .json .yml .yaml .txt .html .htm .css .js防止误改二进制或敏感文件原子写入_atomic_writeL245-L260临时文件 os.fsyncos.replace避免写入中断导致文件损坏自动备份_backup_fileL223-L243每个被改/被删文件在修改前备份到repo/.helloagents/backups/时间戳/保留相对路径结构并加.bak后缀规模限制applyL113-L121单补丁最多 10 个文件max_files、最多 800 行变更max_total_changed_lines冲突检测_apply_hunk/_find_subsequenceL424-L494Update 上下文精确匹配失败即报错并附带检索提示行尾空白归一化二次匹配兜底因此格式校验Begin/End 标记、操作行、hunk 上下文只是第一层闸门其后的每次写入都经过路径、后缀、规模、备份、原子性的层层把关。格式不合法就被拒绝不是缺陷而是这套安全模型的有意设计——宁可失败留痕也不做不可控的写入。排查清单当再次看到 Patch must start with *** Begin Patch结合本笔记的失败场景与源码逻辑遇到该错误时按以下顺序排查检查第一行补丁文本第一行去掉空行后必须是裸的*** Begin Patch。前面不能有冒号、说明文字、反引号不能与 End 标记挤在同一行。react.md 中的错误对比小节错误1补丁前有冒号错误2补丁前有文字在同一行错误3没有空行分隔是复现该报错的最常见三种写法。检查围栏虽然解析器容忍/patch/text围栏但前提是围栏内的 Begin/End 标记本身完好。如果标记被压缩成*** Begin Patch ... *** End Patch这种单行残缺形式围栏容错也无能为力。最稳妥的做法是完全不使用代码块包裹补丁system.md 规则 5。检查操作行*** Add File:/*** Update File:/*** Delete File:之后必须跟仓库相对路径Add/Update后不能空操作Delete后不要跟内容。检查结尾最后一行必须是*** End Patch其后仅允许空行或围栏否则会看到配套错误Patch must end with *** End Patch。检查用户输入与目标文件本次用户需求是修改testDemo下的 HTML 为中文并添加 JS 效果。若补丁路径写错如把相对路径写成绝对路径、或指向仓库外即便格式合法也会在_safe_path/ 后缀白名单处被拦截——这两类错误消息分别是Path escapes repo_root与Disallowed file suffix。善用失败笔记每次失败都会在.helloagents/notes/下生成一条带patch_failed标签的 blocker 笔记。通过context_fetch[{sources:[notes], query:patch_failed}]或直接查看 notes 目录可以回溯所有失败现场避免同类问题反复发生。小结一张只有十几行的 blocker 笔记串联起了 HelloAgents Code Agent CLI 完整的补丁链路从 system.md 与 react.md 定义的格式规范到 hello_code_cli.py 的提取、规范化、确认三步前置管线再到 apply_patch_executor.py 中宽容开头、精确匹配、安全兜底的解析与执行策略最后落到 note_tool.py 的失败留痕机制。Patch must start with *** Begin Patch这一报错的可贵之处在于它把模型格式幻觉这类模糊问题转化为一个可精确解释、可稳定复现、可对照排查的确定性错误。理解它就等于理解了整个补丁协议的边界而这份边界正是代码智能体在仓库中安全落盘的最后防线。【免费下载链接】hello-agents 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考