You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
edit tool: BlockAnchorReplacer threshold=0.0 causes silent overwrite of user manual edits (TOCTOU) #29726
The edit tool's BlockAnchorReplacer (replacer #3 in the 9-step chain at packages/opencode/src/tool/edit.ts) silently accepts files that have changed between the agent's last Read and the Edit call. It uses first-line/last-line anchors with SINGLE_CANDIDATE_SIMILARITY_THRESHOLD = 0.0 for single-candidate blocks.
When the user manually inserts a comment or line inside a multi-line block (between the agent's Read and Edit), the edit succeeds silently and overwrites the user's change instead of failing with oldString not found.
Root cause — two gaps in packages/opencode/src/tool/edit.ts:
BlockAnchorReplacer threshold 0.0 for single candidates — when there's only one block with matching first/last line anchors, any middle content passes the match. A Levenshtein distance of 0.0 threshold is effectively "match everything between the anchors." User-added lines in the middle don't block the match.
No TOCTOU protection — the file is read fresh at edit.ts#L122 and written at edit.ts#L151 with zero guards between:
The inotify watcher (@parcel/watcher, configured in file/watcher.ts) fires events on change but the edit tool never subscribes to them
The semaphore lock (line 88) only serializes concurrent edit tool calls within the same process — it does NOT guard against external (user-editor) mutations
Steps to reproduce
Agent reads a file containing a multi-line block with unique first/last lines (e.g. dirs=() to done)
User manually inserts a comment (e.g. # Exclude comments and blank lines) inside that block via their editor
Agent calls edit with oldString matching the pre-change block
Expected: Edit fails with oldString not found in content because the file has changed.
Actual: Edit succeeds — BlockAnchorReplacer matches the block by first/last line anchors (single candidate, threshold 0.0), replaces the entire block, and silently drops the user's inserted line.
Suggested fix
Option A — Raise the single-candidate threshold from 0.0 to a stricter value (e.g. ≥ 0.9) so that user-added lines in the middle cause the replacer to fall through to the next replacer instead of silently matching.
Option B — Add an mtime guard: after reading the file, compare info.mtimeMs against a stored last-read timestamp. If the mtime changed, fail with a clear error.
Option C — Content snapshot: track the agent's last-read hash per file, verify current content matches before applying the edit.
Activity
github-actions commentedon Jul 28, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.