datawhalechina/hello-agents · error · PatchApplyError

Patch must end with '*** End Patch'

Error message

Patch must end with '*** End Patch'

What it means

Raised by the patch parser when the text, after trimming trailing blanks/fences and back-scanning for the last '*** End Patch' marker, does not end with that exact line. It complements the Begin-Patch check and exists because truncated LLM output commonly loses the closing fence.

Source

Thrown at Co-creation-projects/YYHDBL-HelloCodeAgentCli/code_agent/executors/apply_patch_executor.py:301

        # 如果仍未以标头开头,尝试向下寻找标头并截取
        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'")
        # 同样跳过结尾的围栏/空行
        while lines and lines[-1].strip() in {"", "```"}:
            lines = lines[:-1]
        if not lines or lines[-1].strip() != "*** End Patch":
            # 如果末尾未对齐,尝试在中间找到最后一个 End 标记
            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'")

        ops: List[Tuple[str, str, str]] = []
        i = 1
        while i < len(lines) - 1:
            line = lines[i]
            if line.startswith("*** Add File: "):
                path = line[len("*** Add File: ") :].strip()
                i += 1
                buf: List[str] = []
                while i < len(lines) - 1 and not lines[i].startswith("*** "):
                    # 兼容两种格式:
                    # 1) 规范形式:以 '+' 开头
                    # 2) 宽松形式:直接给出正文(模型有时会省略 '+')
                    if lines[i].startswith("+"):
                        buf.append(lines[i][1:] + "\n")
                    else:
                        buf.append(lines[i] + "\n")
                    i += 1

View on GitHub (pinned to 606a07d341)

Solutions

  1. Append a literal '*** End Patch' line to the patch text before parsing.
  2. Raise max_tokens / reduce patch size so the model can finish the block.
  3. If truncation is routine, append the closer programmatically after detecting a Begin without End.

Example fix

# before
patch_text = model_output  # truncated, no End marker

# after
if '*** End Patch' not in patch_text:
    patch_text = patch_text.rstrip() + '\n*** End Patch'
result = executor.apply(patch_text)
Defensive patterns

Strategy: validation

Validate before calling

lines = [l.strip() for l in patch_text.splitlines()]
if '*** End Patch' not in lines:
    if '*** Begin Patch' in lines:  # truncated but started
        patch_text = patch_text.rstrip() + '\n*** End Patch'
    else:
        raise ValueError('no patch markers at all; regenerate')

Try / catch

try:
    executor.apply(patch_text)
except PatchApplyError as e:
    if "must end with" in str(e):
        patch_text = patch_text.rstrip() + '\n*** End Patch'
        executor.apply(patch_text)
    raise

Prevention

When it happens

Trigger: Model output truncated by max_tokens so '*** End Patch' was never emitted; a trailing code fence '```' after End is fine, but a missing End marker entirely is not; the End line has trailing whitespace variants — strip() handles those — or was replaced with '*** END PATCH'.

Common situations: max_tokens too small for large patches; streaming code that stops on first '*** End Patch' occurrence then reassembles wrongly; users hand-editing patches and dropping the closer.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/870d6aff0b73bc42. Report an issue: GitHub.