fix(opencode): bound apply patch diff metadata - #46656
cyrilialab-prog wants to merge 2 commits into
Conversation
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
The following comment was made by an LLM, it may be inaccurate: Based on my search, I found one related PR: PR #41734: fix(opencode): omit unsafe delete diffs Why it's related: The current PR (46656) explicitly mentions that it "Builds on and supersedes #41734." This is not a duplicate—rather, PR #41734 is a predecessor that handles the initial part of the fix (protecting deletes by checking extension, file size, and content sniff), while PR #46656 extends this with comprehensive bounds for all change types and explicit truncation markers. No actual duplicate PRs found. |
09350a7 to
0badb37
Compare
|
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
|
@cyrilialab-prog - would you mind re-submitting this PR, this time using the correct PR template? I just had several crashes due to this issue, and it would be great to see this fixed! Seeing as you have already done the work, using the appropriate template would seem like a small hurdle to overcome :) |
fix(opencode): bound apply patch diff metadata
Closes #41733. Builds on and supersedes #41734.
Root cause
apply_patchpersisted every file diff twice in tool metadata: once inmetadata.diff(combined) and once inmetadata.files[].patch. For a delete,the "diff" is the entire old file content, so deleting one large or binary file
(for example a 64 MiB archive or a
.binblob full of NUL bytes) wrotehundreds of megabytes of binary text into:
parttable),message.part.updated.1event emitted while the part streamed(
eventtable).In real sessions this grew
opencode.dbpast 2.5 GiB, and the Desktop sidecardied with exit code 5 while rehydrating the session, which surfaced in the UI
as
TypeError: Failed to fetchinfetchMessages/loadMessages.#41734 already stops the worst case for deletes by checking the extension,
file size, and a content sniff before reading. This PR keeps that behavior and
closes the remaining holes:
add/update/movecan still produce apathological diff (for example a single-line file with no newlines, where
diffLinestreats the whole blob as one change).metadata.diffwas unbounded regardless of per-file limits, soa multi-file patch could still aggregate gigabytes.
What this PR does
isBinaryFileintopackages/opencode/src/util/binary.ts, and for deletes check extension,stat.size, and content NUL/printable ratio before reading; omit thecontent diff when binary or larger than 1 MiB.
is bounded to
MAX_DIFF_BYTES(1 MiB) and the combined diff toMAX_TOTAL_DIFF_BYTES(2 MiB), with explicit markers:binary file deleted (N bytes; content diff omitted)oversized file deleted (N bytes; content diff omitted)[diff truncated: showing first N of M bytes]Truncation happens at line boundaries, so consumers never see a half-written
hunk; a single line larger than the cap is dropped entirely in favor of the
marker. The metadata always stays valid UTF-8 and JSON-encodable. The UI
degrades to a short marker instead of a crash; the actual file operation
result is unchanged.
Tests
packages/opencode/test/tool/apply_patch.test.ts, 35 passing:contains the exact byte count, both result and permission metadata bounded,
file removed)
not just extensions)
its real diff, large file gets the marker, permission metadata equals result
metadata)
(per-file diffs stay intact, combined diff capped at 2 MiB + marker)
store (
Session.updatePart-> SQLite ->MessageV2.page; persisted toolstate stays under 8 KiB)
and the
PermissionV1JSON-encodability checkVerification commands (from
packages/opencode):Compatibility risks
metadata.diffandmetadata.files[].patchcan now contain omissionmarkers instead of full content. Consumers that parse these strings as
unified diffs (UI diff renderers, external tooling reading tool metadata)
will see a short
Index:-style block or a truncated diff with an explicitmarker. No schema change: both fields remain strings.
deletionsis0for omitted deletes (we intentionally do not read thefile just to count lines). Consumers relying on exact deletion line counts
for deleted oversized/binary files will see 0; the byte size is in the
marker.
fix is write-side only and does not migrate data.
Migration plan for existing databases
The fix prevents new bloat. Databases already affected need a one-time
cleanup, run with the server stopped:
Longer term, bounded event-log compaction would help every tool, not just
apply_patch(see #33356 and the closed #36710, closed only by automatedcleanup criteria, plus #40861/#42771 which address related summary-diff
payloads). Happy to split that into a separate PR if preferred.