Bad apply_patch error message "Invalid patch: The first line of the patch must be '*** Begin Patch'" results in agent looping
What version of Codex CLI is running?
codex-cli 0.145.0
What subscription do you have?
N/A
Which model were you using?
gemma4 (google/gemma-4-31B-it, bf16) via vllm
What platform is your computer?
Linux 7.0.0-28-generic x86_64 unknown (Ubuntu multipass VM running on Ubuntu)
What terminal emulator and version are you using (if applicable)?
tmux
Codex doctor report
What issue are you seeing?
gemma4 tried to modify a file using the update_patch tool. It got back the error "Invalid patch: The first line of the patch must be '*** Begin Patch'" however the string it provided does start with that, so it just continued to loop passing the same arguments again and again until I stopped it.
I'm not familiar with the apply_patch tool, so I'm assuming that maybe the arguments are not in the right format (should it just be a string being passed, or an object with named parameters?). If the arguments are not correct, then the error message should be clearer and explain this, and not just say that the patch is incorrect. If the arguments are correct, then it's not clear why it would get this message.
Here's a slightly condensed version of the output I see:
• Ran apply_patch '{"command":["apply_patch","*** Begin Patch\n*** Update File: .github/workflows/build-and-test.yml
│ … +1 lines
└ Invalid patch: The first line of the patch must be '*** Begin Patch'
• Ran apply_patch '{"command":["apply_patch","*** Begin Patch\n*** Update File: .github/workflows/build-and-test.yml
│ … +1 lines
└ Invalid patch: The first line of the patch must be '*** Begin Patch'
• Ran apply_patch '{"command":["apply_patch","*** Begin Patch\n*** Update File: .github/workflows/build-and-test.yml
│ … +1 lines
└ Invalid patch: The first line of the patch must be '*** Begin Patch'
• Ran apply_patch '{"command":["apply_patch","*** Begin Patch\n*** Update File: .github/workflows/build-and-test.yml
│ … +1 lines
└ Invalid patch: The first line of the patch must be '*** Begin Patch'
And here's a screenshot of it in the CLI:
<img width="1135" height="366" alt="Image" src="https://github.com/user-attachments/assets/b7880818-23ed-4ba7-9067-4891377263cb" />
What steps can reproduce the bug?
I suspect it will be hard to reproduce on-demand, but the above output will hopefully make the issue clear.
What is the expected behavior?
The error message should be clear enough that the agent can resolve it. When it thinks it's calling the tool correctly and the error message only describes something it is already doing, it may just loop trying the same thing over and over.
Additional information
_No response_
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Analysis (community)
Residual analysis for #35361 (bad
apply_patch“first line must be*** Begin Patch” error → agent retry loops) under docs/contributing.md — invitation-only. No unsolicited PR.Root-cause hypothesis
The apply_patch parser enforces a strict first-line marker with only lightweight
trim(), the lenient path only peels a narrow heredoc shape, and parse failures are returned viaRespondToModelas plain strings — so common model mistakes (markdown fences, leading prose, missing spaces) produce a generic error that invites another nearly identical attempt.Verified at HEAD
4c43465133:1. Exact error site.
codex-rs/apply-patch/src/parser.rs:BEGIN_PATCH_MARKER = "*** Begin Patch"check_start_and_end_lines_strict: if first line ≠ marker →InvalidPatchError("The first line of the patch must be '*** Begin Patch'")invalid patch: …via thiserror.Only trim on first/last lines — does not strip:
`/`diff`)***Begin Patch(missing space) — fails equality2. Lenient path is narrow.
check_patch_boundaries_lenientonly peels<<EOF/<<'EOF'/<<"EOF"when the last line ends withEOF. Not markdown, not nested prose. So the failure mode reported in the issue (agents wrapping patches in fenced blocks or chatter) is outside the lenient envelope.3. Retry fuel — handler surface.
codex-rs/core/src/tools/handlers/apply_patch.rsmaps parse failure toFunctionCallError::RespondToModel(format!("apply_patch verification failed: {parse_error}"))(and a streaming finish path
failed to parse apply_patch: …). That is a normal tool-error → model follow-up surface: the turn layer continues with needs_follow_up style re-entry, so the model is invited to retry without a structured “fatal envelope / abort this format” signal.Contrast:
format_exec_output_for_modelalways prefixes Exit code + wall time for exec failures; apply_patch parse failures are string-only and do not include a first-line preview.4. Honest scope on “looping”.
OSS enables retry fuel; it does not implement an infinite server-side apply_patch loop. The agent-looping symptom is model policy + identical error + no progressive diagnostics. Still a high-value residual: better errors (and optional light lenience) would cut wasted turns.
Ranked fix outline (all OSS):
`fences; suggest “strip fences / ensure exact*** Begin Patch`”.apply_patch.invalid_envelopeso prompts/models can switch strategy once.Fail-first tests: (1) fenced patch → error mentions fence or first line; (2)
***Begin Patch(no space) → actionable hint; (3) identical envelope fail N times → stop or escalate once if (4) lands.Happy to draft the error-string tests if useful. No unsolicited PR.
@bbingz I don't understand how stripping backticks will help, there are none in the first line reported above:
I presume there is something else wrong, but I couldn't find any examples of what a valid
apply_patchcall should look like.@DanTup Thanks for the careful pushback — you were right to question the backtick/fence framing for your log, and I appreciate you calling that out with the exact
Ran apply_patch '…'line.You're correct: stripping markdown backticks would not help the call you showed. There are no backticks in that payload. What matters is the leading
{.What the parser actually saw
Codex's
apply_patchtool is a freeform tool: it passes the entire custom-tool input string straight into the patch parser and does not unwrap a JSON object like{"command":[...]}. The tool description also says not to wrap the patch in JSON.In the shape from your report:
the string that reaches the first-line check begins with
{, not*** Begin Patch. The marker you can see is one layer deeper — inside a nested JSON string (command[1]). So the error:is technically true for the bytes received, but it is misleading for a human (and for a model): it does not say “you sent a JSON wrapper whose first character is
{,” so the agent reasonably thinks it already started with the right marker and retries the same shape.I should not have led with fence/backtick stripping for this issue; that is only a generic example of another invalid envelope, not the diagnosis of your log. Sorry for the noise there.
What a valid freeform
apply_patchlooks likeSend raw patch grammar only as the tool input (no JSON object, no
commandarray, no nestedapply_patch, no markdown fence around the body):Conceptually:
Not:
(If something goes through the shell tool instead, the supported pattern is a heredoc
apply_patch <<'PATCH' ... PATCH, not a quoted JSON blob as the single argument.)Why the loop happens
There is no dedicated server-side “infinite apply_patch loop.” A failed tool result is returned to the model, which is invited to try again. With an error that does not surface the leading
{/ double-wrapping, a smaller model can keep resending the same nested JSON. Better diagnostics (preview first bytes; detect JSON/commandnesting explicitly) would help more than fence stripping for this case.Happy to refine further if you have a raw tool-call capture from the provider. Thanks again for the precise challenge — it fixed the attribution on this thread.
Ah, that sounds more likely. I wasn't sure what the format was supposed to be.
In that case, I think it would've helped the agent if:
{, the message noted that it looks like JSON but should not beI suspect if the error had been more specific like this, the model would've attempted to correct the format instead of just sending the same thing again.
Hi Codex team — I’d like to contribute a focused fix for this issue.
The proposed change keeps parsing behavior unchanged. When the trimmed first line begins with {, both the full and streaming apply_patch parsers return an actionable diagnostic explaining that the input looks JSON-wrapped and that raw patch text must be passed directly. Non-JSON errors remain unchanged.
I have regression coverage for both parser paths. The focused crate suite passes 86/86 tests, along with Clippy and formatting checks.
Would a Codex maintainer like me to submit this as a PR? If so, please explicitly invite me to open it in accordance with docs/contributing.md.