Skip to content

[FEATURE]:edit: success gives no signal whether oldString matched exactly or via fuzzy fallback — surface the match strategy #44999

Closed

Description

@polkamu-ops

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Summary

The edit tool's fuzzy replacer chain is an intentional design to tolerate LLM whitespace/indentation drift — this request does not ask to change or remove it. The gap is observability: when oldString matches only via a fuzzy fallback, the tool reports the same plain "Edit applied successfully." as an exact byte match. Callers (human or spec-driven automation) get zero signal that their oldString did not literally exist in the file, so a wrong oldString (e.g. drifted indentation) is silently absorbed and only surfaces later via manual diff review.

Companion bug (filed as #44996): the not-found error text falsely claims exact whitespace matching.

Why this is not a duplicate (fuzzy-chain family)

Issue Where the defect is Nature
#41872 (open) Multiple equivalent candidates; tool silently commits the first Fuzzy picks the wrong target (ambiguity bypass)
#32559 (closed) Stale/incorrect oldString still matches False match applied
#31785 (closed) Single-line oldString bypasses a guard Guard bypass hole
#42908 (open) No match; error gives nothing to correct against Failure path unhelpful
This request A single candidate; fuzzy matches it and applies Success path opaque — fuzzy vs exact success indistinguishable

Those pursue "make fuzzy more conservative". This asks instead: make a fuzzy hit recognizable, without changing the tolerance.

Reproduction

File:

alpha
   beta_line
foo  bar
<TAB>tab_indented
gamma
# File content oldString Result Matched via
1 leading indent beta_line (3sp) beta_line (4sp) applied LineTrimmedReplacer
2 control — nonexistent_text_xyz rejected —
3 internal space foo bar (2sp) foo bar (1sp) applied WhitespaceNormalizedReplacer
4 tab vs spaces <TAB>tab_indented tab_indented (4sp) applied LineTrimmedReplacer

Real-world impact: in a spec-driven refactor, a spec's oldString had 4-space indentation while the file had 3 spaces. The edit silently succeeded and rewrote the line with the spec's wrong indentation; the corruption was only caught later by manual base-branch diff review.

Source analysis (packages/opencode/src/tool/edit.ts)

replace() walks nine ordered replacers — SimpleReplacer (byte-exact) first, then eight tolerant fallbacks. Two causes of the opacity:

  1. replace() returns only the new content string; which replacer matched is known inside the loop but discarded at return.
  2. The tool's output is the constant "Edit applied successfully."; ctx.metadata carries only diff/filediff/diagnostics.

Proposal

The tool already returns { metadata, title, output }, so this enriches existing fields — no return-contract change.

Recommended: detect at the tool layer (zero change to replace())

Keep replace(): string as-is. In execute, after a successful replacement:

const wasExact = contentOld.includes(old)   // `old` already line-ending-normalized
const strategy = wasExact ? "exact" : "fuzzy"

Sound because SimpleReplacer is first and yields oldString verbatim, so includes ⇔ exact-byte match winning; ambiguous multi-match inputs throw before returning. Then:

let output = "Edit applied successfully."
if (strategy !== "exact")
  output += `\nNote: oldString did not match byte-for-byte; applied via fuzzy fallback. Verify the region if unintended.`
// + add matchStrategy: strategy to ctx.metadata

Non-breaking; the note is informational, placed after the success line. Caveat: a rare edge (exact ambiguous, fuzzy unique) may report exact — acceptable for an observability signal.

Reference alternative: return { content, strategy } from replace()

For the exact replacer name, tag each replacer in the chain and return it. Listed for reference only — replace is exported and used by edit.test.ts, so this changes its signature and call sites (larger blast radius).

Optional: strict mode

An exact: true parameter restricting the chain to SimpleReplacer, for automation that needs byte-exact semantics.

Related

Environment

  • opencode: 1.18.21
  • OS: Windows (win32)
  • Tool: built-in edit
  • Confirmed via controlled probes.

Activity

  1. github-actions commented on Aug 25, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. polkamu-ops commented on Aug 25, 2026

    @polkamu-ops
    Author

    duplicate of #34424, findings contributed there

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

Metadata

Metadata

Assignees

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