can1357/oh-my-pi · error · ToolError
STOP. Edits to ${path} have been a byte-identical no-op ${co
Error message
STOP. Edits to ${path} have been a byte-identical no-op ${count} times in a row — the patch body matches the file at the targeted lines and the soft hint did not break the cycle. Cease re-issuing this payload. Either the intended change is already on disk (move on), or your anchor is wrong (re-read the file with `read` to observe the current line numbers and tag, then author a different edit). This exact payload will keep being rejected until it changes. What it means
The edit tool tracks consecutive byte-identical no-op edits per file+payload hash. When the same patch is re-applied N times and each commit reports op === 'noop' (the body already matches the file at the targeted lines), recordNoopEdit escalates and this ToolError is thrown telling the model to stop re-sending the payload, distinguishing 'change already applied' from 'wrong anchor'.
Source
Thrown at packages/coding-agent/src/edit/hashline/execute.ts:239
// Named registers persist across edit calls; the anonymous register is
// batch-local. Each batch starts without anonymous state and publishes
// named registers only after writes land.
const sessionClipboard = getEditClipboard(options.session);
const clipboard = startClipboardBatch(sessionClipboard);
// Single-section fast path: prepare, commit, render.
const inputHash = hashPatchInput(options.input);
if (patch.sections.length === 1) {
fs.setBatchRequest(narrowBatchRequest(options.batchRequest, true));
const prepared = await patcher.prepare(patch.sections[0], clipboard);
const sectionResult = await patcher.commit(prepared);
await observeAppliedSection(options.onApplied, prepared, sectionResult);
commitClipboard(clipboard, sessionClipboard);
if (sectionResult.op === "noop") {
const { count, escalate } = recordNoopEdit(options.session, sectionResult.canonicalPath, inputHash);
if (escalate) {
throw new ToolError(noChangeLoopDiagnostic(sectionResult.path, count));
}
return renderSection(sectionResult, undefined, prepared.section.path).toolResult;
}
resetNoopEdit(options.session, sectionResult.canonicalPath);
return renderSection(sectionResult, fs.consumeDiagnostics(sectionResult.path), prepared.section.path).toolResult;
}
// Multi-section: prepare every section up front so we fail fast before
// any write hits the filesystem. One batch-local register spans the batch,
// so `CUT` in one section feeds a register-backed `PUT` in a later one.
const prepared: PreparedSection[] = [];
// Register state after each section's prepare. Commits are non-atomic: a
// mid-batch write failure leaves earlier sections on disk, so the session
// register must reflect exactly the landed prefix — content a landed CUT
// deleted would otherwise be lost.
const sectionStates: Clipboard[] = [];
for (const section of patch.sections) {
prepared.push(await patcher.prepare(section, clipboard));View on GitHub (pinned to 9690622007)
Solutions
- Stop resending the same payload — verify the intended change with the read tool first.
- If the change is genuinely missing, re-read to get fresh line numbers and tags, then author a DIFFERENT edit (changed old/new text or anchor).
- If the change is already on disk, move on to the next task step.
Example fix
// before (identical retry, already applied) @@@ src/a.ts:1a2b - fixed line + fixed line // after (re-read, then real edit) // read src/a.ts -> fresh tag e5f6, see line moved @@@ src/a.ts:e5f6 - old unchanged line + actually new content
Defensive patterns
Strategy: retry
Validate before calling
// before re-issuing an edit, verify the change is not already applied
const live = await Bun.file(absPath).text();
if (live.includes(newText)) return { skipped: true, reason: 'change already present' }; Try / catch
try {
return await executeHashlineSingle(options);
} catch (e) {
if (e instanceof ToolError && e.message.includes('byte-identical no-op')) {
// do NOT retry the same payload; re-read and confirm or author a different edit
const fresh = await readForTag(path);
return planNewEditFromLiveContent(fresh);
}
throw e;
} Prevention
- Never resend an identical edit payload after a no-op response.
- Read the file after each successful edit to refresh anchors and confirm state.
- Treat a first no-op hint as a signal to verify on-disk state before retrying.
When it happens
Trigger: Re-issuing an identical edit after it already succeeded (ops now match existing content), repeatedly targeting lines whose content already equals the replacement, or retrying the same input after a soft no-op hint without changing anything.
Common situations: Agents retrying a 'failed-looking' edit that actually succeeded; fuzzy matches where the intended change is semantically already present; stale model context claiming old content that was already replaced.
Related errors
- ${message} || Unable to read ${sectionPath}
- Missing hashline snapshot tag for ${sectionPath}; use `${HL_
- Multiple hashline sections resolve to the same file (${previ
- No hashline sections found in input.
- File not found: ${relativePath}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/17e7e7a34bd20cde.
Report an issue: GitHub.