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

  1. Stop resending the same payload — verify the intended change with the read tool first.
  2. 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).
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/17e7e7a34bd20cde. Report an issue: GitHub.