can1357/oh-my-pi · error
Operation ${operationNumber} adds or removes whole lines but
Error message
Operation ${operationNumber} adds or removes whole lines but MATCH did not match byte-for-byte. Re-read the region and copy its exact indentation. What it means
The pattern contains whole-line markers (gap-based line matching) so it is expected to match the file byte-for-byte at the line level, but raw candidate collection found zero matches. The library throws this with explicit guidance: re-read the target region and copy its exact indentation, because the lines as written do not appear verbatim in the file.
Source
Thrown at packages/coding-agent/src/edit/sloppy.ts:2418
);
});
}
function locate(
content: string,
pattern: ParsedPattern,
operation: Operation,
operationNumber: number,
path: string,
exclusions?: ReadonlyArray<{ start: number; end: number }>,
): Candidate[] {
const normalized = normalizeText(content);
const raw = collectCandidates(content, normalized, pattern, "raw");
if (raw.overflow) {
throw new Error(`Operation ${operationNumber} pattern is too broad; add another distinctive ${GAP} fragment.`);
}
if (raw.candidates.length === 0 && hasMarkerLines(operation.sourcePatternText)) {
throw new Error(
`Operation ${operationNumber} adds or removes whole lines but MATCH did not match byte-for-byte. Re-read the region and copy its exact indentation.`,
);
}
if (raw.candidates.length === 0 && pattern.literalFallback) {
const exact = exactOccurrences(normalized.text, pattern.literalFallback.normalized);
if (exact.length > 0 && (operation.all || exact.length === 1)) {
const fallbackCandidates = exact.map(occurrence => {
const matchStart = sourceStart(normalized, occurrence.start, 0);
const matchEnd = sourceEnd(normalized, occurrence.end, content.length);
const fallbackStart = occurrence.start + pattern.literalFallback!.selectionStart;
const fallbackEnd = occurrence.start + pattern.literalFallback!.selectionEnd;
const start =
pattern.literalFallback!.selectionStart === pattern.literalFallback!.normalized.length
? matchEnd
: sourceStart(normalized, fallbackStart, matchEnd);
const end =
pattern.literalFallback!.selectionEnd === pattern.literalFallback!.normalized.length
? matchEndView on GitHub (pinned to 9690622007)
Solutions
- Re-read the target region and copy the lines byte-for-byte, preserving exact leading whitespace.
- Check indentation characters: the file may use tabs where your pattern uses spaces (or different depth).
- If only part of the region matters, convert the pattern to partial-line matching with `...` gaps instead of whole-line markers.
Example fix
// before (pattern, file uses 2-space indent) const pattern = " return sum(a, b);"; // after const pattern = " return sum(a, b);";
Defensive patterns
Strategy: validation
Validate before calling
// Verify each pattern line exists byte-for-byte in the current file content
// (after refreshing the file view) before applying.
const missing = patternLines.filter(l => !content.includes(l));
if (missing.length > 0) {
throw new Error(`lines not found verbatim: ${JSON.stringify(missing)}`);
} Try / catch
try {
await applySloppyEdit({ pattern });
} catch (err) {
if (err instanceof Error && err.message.includes("byte-for-byte")) {
// re-read the region, copy exact indentation (tabs vs spaces), retry
}
throw err;
} Prevention
- Re-read the file region immediately before writing whole-line patterns.
- Match indentation exactly — same characters (tabs/spaces) and same depth.
- Watch for stale file views after other tools or prior edits changed the file.
- Avoid hand-typing lines; copy them from the actual source.
When it happens
Trigger: An operation that adds/removes whole lines (pattern built from marker lines via `hasMarkerLines`) where none of the literal lines match the file exactly — typically due to wrong indentation, tabs vs spaces, or stale content after the file changed.
Common situations: Working from an outdated file view (file edited since last read); mixing tab-indented file content with space-indented pattern (or vice versa); off-by-one indentation when copying nested blocks; CRLF vs LF in hand-crafted patterns.
Related errors
- ${operation.all ? `Operation ${operationNumber} ${OPENER}* f
- Operation ${operationNumber} pattern is too broad; add anoth
- Operation ${operationNumber} did not match ${path}: your lin
- Operation ${operationNumber} is ambiguous: ${candidates.leng
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains a provider id wit
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/6c9382a393b7c42c.
Report an issue: GitHub.