Skip to content

Commit b3f1deb

Browse files
authored
fix(opencode): skip end bounds check on empty file in validateEdits (#752)
When a file has no content (lineCount === 0), any edit is a pure insert and the end field is semantically irrelevant. The previous check rejected payloads where end was present on an empty file because end > lineCount always evaluated true, breaking first-call submit_plan invocations from agents or frameworks that include end unconditionally. - Skip end > lineCount validation when lineCount === 0; applyEdits handles it via splice clamping - Add applyEdits test: edit on empty file with start=1, end=1 produces correct output - Add validateEdits test: passes for empty file with start=1 and end=1 Fixes #742
1 parent 82636e1 commit b3f1deb

2 files changed

Lines changed: 16 additions & 1 deletion

File tree

apps/opencode-plugin/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,11 @@ export function validateEdits(existingLines: string[], edits: PlanEdit[]): strin
185185
if (!Number.isInteger(edit.end) || edit.end < edit.start) {
186186
return `end (${edit.end}) must be >= start (${edit.start})`;
187187
}
188-
if (edit.end > lineCount) {
188+
// On an empty file (lineCount === 0) every edit is a pure insert;
189+
// end is semantically meaningless and applyEdits handles it via splice
190+
// clamping. Rejecting here breaks first-call payloads where the agent
191+
// or framework includes end (see #742).
192+
if (edit.end > lineCount && lineCount > 0) {
189193
return `end (${edit.end}) exceeds file length (${lineCount})`;
190194
}
191195
}

apps/opencode-plugin/submit-plan.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ describe("applyEdits", () => {
5353
expect(result).toEqual(["hello"]);
5454
});
5555

56+
test("edit on empty file (start=1, end=1) — splice clamps gracefully (#742)", () => {
57+
const result = applyEdits([], [{ start: 1, end: 1, content: "# Plan\nGoals" }]);
58+
expect(result).toEqual(["# Plan", "Goals"]);
59+
});
60+
5661
test("content with trailing newline produces trailing empty string", () => {
5762
const result = applyEdits([], [{ start: 1, content: "line1\nline2\n" }]);
5863
expect(result).toEqual(["line1", "line2", ""]);
@@ -126,6 +131,12 @@ describe("validateEdits", () => {
126131
test("passes for empty file with start=1", () => {
127132
expect(validateEdits([], [{ start: 1, content: "hello" }])).toBeNull();
128133
});
134+
135+
test("passes for empty file with start=1 and end=1 (#742)", () => {
136+
// Agent or framework may include end on first call; validation should
137+
// not reject it since applyEdits handles this via splice clamping.
138+
expect(validateEdits([], [{ start: 1, end: 1, content: "# Plan\nGoals" }])).toBeNull();
139+
});
129140
});
130141

131142
// ── formatWithLineNumbers ──────────────────────────────────────────────────

0 commit comments

Comments
 (0)