Skip to content

edit: no tolerance for retyped oldString with multi-character typographic drift (-- vs em dash, -> vs arrow, read prefixes) #56096

Open

Description

@fluffyspace

Summary

The edit tool cannot match oldString retyped with common multi-character typographic drift — -- for an em dash —, -> for →, * for ·, leftover read-style 12: line prefixes — and the failure is total: the existing fuzzy chain never covers single-line candidates or these equivalences. This is very frequent for markdown files containing Unicode punctuation and costs a read→edit→fail→retry round-trip per occurrence. I'd like a Unicode-normalization fallback in the matcher (and/or nearest-match snippets in the error, see #42908).

Environment

  • opencode version: v2.0.30
  • OS: Windows_NT 10.0.26300 (win32 x64)
  • Terminal: Unavailable (agent-driven sessions; TUI not involved in the failure)
  • Shell: cmd.exe
  • Install/channel: latest
  • Active plugins: a user-side workaround for this exact gap (see Additional Context); the failure matrix below was captured with that plugin disabled

Reproduction

demo.md (note the real U+2014, U+2192, U+00B7 and the blank middle line):

Phase 2 — deploy the service and register receivers
Lockscreen → unlock on tap with care please
Battery threshold set to 15% · do not disturb mode enabled
Config block alpha — value one here

Config block beta — value two more
Trailing line for uniqueness xyz

Call edit the way models naturally emit it after seeing the file through read (they retype, and ASCII-ify):

# oldString used (vs file text above) built-in result
A Phase 2 -- deploy the service and register receivers ❌ Could not find oldString
B Lockscreen -> unlock on tap with care please ❌ Could not find oldString
C Battery threshold set to 15% * do not disturb mode enabled ❌ Could not find oldString
D Config block alpha -- value one here\n\nConfig block beta -- value two more ❌ Could not find oldString
E 4: Config block alpha — value one here (read line-number prefix left in) ❌ Could not find oldString

All five were verified failing with the workaround plugin disabled and succeeding with it enabled.

Cases A–C show the single-line path has no tolerance at all beyond exact/1:1-variant matching (BlockAnchorReplacer is block-based), and the retry's "typographic dash/quote variants" mapping is 1:1 per character, so length-changing equivalences (-- ↔ —, -> ↔ →, * ↔ ·) never match. E (line prefixes) has no handling anywhere. D is plausibly the blank-line denominator bug in #45199.

Expected Behavior

  • Before failing, attempt a canonicalized fallback: compare a whitespace-stripped, NFKD-normalized form of oldString against the file with a small equivalence table (all dashes → -, →/⇒ → ->, curly quotes → straight, … → ..., ·/• → *, …). Edit the byte-exact on-disk substring found; existing uniqueness/ambiguity rules stay as they are. This should also cover 1–2-line oldString, not only multi-line blocks, and should ignore accidentally included N: read prefixes.
  • When nothing matches even fuzzily, include one or two closest regions (file:line + verbatim snippet) in the error so the model self-corrects in one step instead of re-reading the whole file.

Actual Behavior

Every drift class above fails outright with:

Could not find oldString in demo.md. It must match exactly, including whitespace and indentation.

…which (per #44996) also misstates the real contract, and gives no correction hint (see also #42908).

Additional Context

Activity

  1. opencode-agent commented on Oct 16, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed write-up and the failure matrix!

    To sum up the request: when the edit tool finds no exact match, it should fall back to a normalized comparison. That comparison would treat substitutions that change length (--/—, ->/→, */·, .../…) as equal, cover one- and two-line oldString values, ignore leftover N: prefixes from read, and keep the existing uniqueness rules. If nothing matches, the error would show the closest regions of the file.

    Today the tool's retry swaps only one character for one character (typographic quotes, dashes and spaces), plus trailing whitespace, so most of your cases fall outside what it currently handles. I've filed this as a feature request, and maintainers will consider it, including your offer to contribute the matching code. Your tool.execute.before plugin is a reasonable way to handle this in the meantime.

    Related issues:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions