CoplayDev/unity-mcp · error · RuntimeError
anchor not found: {anchor}
Error message
anchor not found: {anchor} What it means
In script_apply_edits, the anchor_insert operation searches for an anchor pattern in the file text using _find_best_anchor_match (a heuristic regex matcher). If no match is found AND the edit's allow_noop flag is False (or absent, since the default is True), this RuntimeError fires with the unmatched anchor. When allow_noop is True (default), a missing anchor is silently skipped (continue), so this error only fires when the caller explicitly set allow_noop=False.
Source
Thrown at Server/src/services/tools/script_apply_edits.py:421
if not text.endswith("\n"):
text += "\n"
text += append_text
if not text.endswith("\n"):
text += "\n"
elif op == "anchor_insert":
anchor = edit.get("anchor", "")
position = (edit.get("position") or "before").lower()
insert_text = edit.get("text", "")
flags = re.MULTILINE | (
re.IGNORECASE if edit.get("ignore_case") else 0)
# Find the best match using improved heuristics
match = _find_best_anchor_match(
anchor, text, flags, bool(edit.get("prefer_last", True)))
if not match:
if edit.get("allow_noop", True):
continue
raise RuntimeError(f"anchor not found: {anchor}")
idx = match.start() if position == "before" else match.end()
text = text[:idx] + insert_text + text[idx:]
elif op == "replace_range":
start_line = int(edit.get("startLine", 1))
start_col = int(edit.get("startCol", 1))
end_line = int(edit.get("endLine", start_line))
end_col = int(edit.get("endCol", 1))
replacement = edit.get("text", "")
lines = text.splitlines(keepends=True)
max_line = len(lines) + 1 # 1-based, exclusive end
if (start_line < 1 or end_line < start_line or end_line > max_line
or start_col < 1 or end_col < 1):
raise RuntimeError("replace_range out of bounds")
def index_of(line: int, col: int) -> int:
if line <= len(lines):
return sum(len(l) for l in lines[: line - 1]) + (col - 1)
return sum(len(l) for l in lines)View on GitHub (pinned to c21bf496bc)
Solutions
- Re-read the current file content and recompute the anchor to match the latest text.
- Set allow_noop=true (or omit it — default is True) so a missing anchor is skipped instead of raising.
- Enable ignore_case=true if the mismatch is case-related.
- Simplify the anchor to a more stable, unique substring (e.g., a method signature line).
Example fix
// before
edits=[{"op": "anchor_insert", "anchor": "class OldName {", "position": "before", "text": "// header\n", "allow_noop": false}]
// after — re-read file, use current anchor, and allow noop
edits=[{"op": "anchor_insert", "anchor": "class NewName {", "position": "before", "text": "// header\n"}] Defensive patterns
Strategy: validation
Validate before calling
import re
def anchor_exists(anchor: str, text: str, ignore_case: bool = False) -> bool:
flags = re.MULTILINE | (re.IGNORECASE if ignore_case else 0)
return re.search(anchor, text, flags) is not None
# Before applying edits, verify anchors against the CURRENT file text:
for edit in edits:
if edit.get("op") == "anchor_insert":
if not anchor_exists(edit["anchor"], current_text, edit.get("ignore_case", False)):
if not edit.get("allow_noop", True):
print(f"Anchor not found and allow_noop is False: {edit['anchor']}") Type guard
import re
def will_anchor_match(edit: dict, text: str) -> bool:
if edit.get("op") != "anchor_insert":
return True
flags = re.MULTILINE | (re.IGNORECASE if edit.get("ignore_case") else 0)
return re.search(edit.get("anchor", ""), text, flags) is not None Prevention
- Re-read the file and compute anchors against the latest text before applying edits.
- Leave allow_noop at its default (True) so missing anchors are skipped, not fatal.
- Use unique, stable anchors (e.g., method signatures) to avoid ambiguity and drift.
When it happens
Trigger: An edit with op=anchor_insert, allow_noop=false, and an anchor string that does not appear in the file (e.g., the file changed since the anchor was computed); the anchor has leading/trailing whitespace that doesn't match; ignore_case is False and the case differs; the anchor was computed against a different version of the file.
Common situations: An AI computed an anchor from a stale file read (file was edited by another process or a prior edit in the same batch shifted content); the anchor has subtle whitespace differences; case sensitivity mismatch; the anchor targets a code region that was already removed.
Related errors
- op is required; allowed: {allowed}. Use 'op' (aliases accept
- replace_range out of bounds
- unknown edit op: {op}; allowed: {allowed}. Use 'op' (aliases
- 'commands' must be a non-empty list of command specification
- batch_execute supports up to {max_commands} commands (config
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/55d320a75fa2b138.
Report an issue: GitHub.