Claude Text Editor Tool File Not Found — Path Fix (2026) | AI Error Hub
Anthropic Claude Computer Use Severity: Medium Client-side execution

Claude Text Editor Tool — File Not Found

Claude's text_editor_20250124 tool call fails because the path doesn't exist, uses relative paths, or escapes the sandbox. Return descriptive errors (with the path Claude tried) so it can self-correct — a bare "file not found" causes retry loops.

Error surface: Client tool execution Category: Computer Use Tool: text_editor_20250124 Last tested: Nov 15, 2026
Quick Fix (TL;DR)

Implement all six commands (view, create, str_replace, insert, undo_edit). Always resolve paths to absolute paths within an allowed sandbox root. Reject path traversal (../). Return errors as strings that describe both what went wrong and what alternatives exist ("File not found: /work/foo.py. Similar files: foo.js, foo.md").

The Error Symptoms

Common failure modes
FileNotFoundError: [Errno 2] No such file or directory: 'src/main.py'
  → Relative path; Claude assumed CWD was project root

PermissionError: [Errno 13] Permission denied
  → Path outside allowed sandbox; or file owned by another user

IsADirectoryError: [Errno 21] Is a directory
  → Claude tried to view /home/user (dir); view returns listing

Claude retries the same failing path 5 times
  → Error message didn't guide correction; add path suggestions

str_replace error: "old_str appears 3 times, must be unique"
  → Common Claude mistake; return count + surrounding context

undo_edit fails: "No previous edit to undo"
  → Undo stack not implemented; return proper error

The text_editor Tool Contract

Claude's text_editor_20250124 tool supports these commands. Your implementation must handle each:

CommandInputsBehavior
viewpath, view_range?Show file with line numbers, or dir listing
createpath, file_textCreate new file with content (fail if exists)
str_replacepath, old_str, new_strUnique replacement of exact string
insertpath, insert_line, new_strInsert after line N (1-indexed; 0 = start)
undo_editpathUndo last edit to this file

What Actually Causes This Error

42%
Relative paths from ClaudeClaude sends "src/main.py"; your CWD is somewhere else. Resolve relative to sandbox root.
22%
Path traversal blockedClaude tried "../etc/passwd" or "/tmp/../root/.ssh/key". Reject with a clear message.
15%
Missing directory in create pathClaude called create with "new/dir/file.py" but "new/dir/" doesn't exist yet.
10%
Bare "not found" error confuses ClaudeWithout file listing / suggestions, Claude guesses wildly.
6%
str_replace non-unique matchCommon code fragment appears multiple times; replace becomes ambiguous.
5%
File encoding issuesNon-UTF-8 files raise UnicodeDecodeError on view.

Fixes That Work (Tested Nov 2026)

1Sandbox Path Resolution

Python · safe path resolver
from pathlib import Path SANDBOX_ROOT = Path("/workspace").resolve() class PathError(Exception): pass def resolve_sandbox_path(path_str): """Resolve a path safely within the sandbox.""" p = Path(path_str) # Resolve relative → absolute under sandbox if not p.is_absolute(): p = SANDBOX_ROOT / p resolved = p.resolve() # Verify still inside sandbox (defeats ../ escapes) try: resolved.relative_to(SANDBOX_ROOT) except ValueError: raise PathError( f"Path {path_str} escapes sandbox {SANDBOX_ROOT}. " f"Use paths under /workspace/." ) return resolved # Use in every command handler def safe_path(path_str): try: return resolve_sandbox_path(path_str) except PathError as e: return None, str(e)

2Complete text_editor Implementation

Python · all commands with helpful errors
from pathlib import Path import difflib edit_history = {} # path -> [previous_content_stack] def handle_text_editor(tool_use): input_ = tool_use.input cmd = input_["command"] path_str = input_["path"] try: path = resolve_sandbox_path(path_str) except PathError as e: return _error(tool_use.id, str(e)) handlers = { "view": _view, "create": _create, "str_replace": _str_replace, "insert": _insert, "undo_edit": _undo, } if cmd not in handlers: return _error(tool_use.id, f"Unknown command: {cmd}") return handlers[cmd](tool_use, path, input_) def _error(tool_use_id, msg): return { "type": "tool_result", "tool_use_id": tool_use_id, "content": msg, "is_error": True } def _ok(tool_use_id, content): return {"type": "tool_result", "tool_use_id": tool_use_id, "content": content} def _view(tool_use, path, input_): if not path.exists(): # Include suggestions parent = path.parent if parent.exists(): candidates = [f.name for f in parent.iterdir()] close = difflib.get_close_matches(path.name, candidates, n=3) hint = f" Did you mean: {close}?" if close else f" Available: {candidates[:10]}" else: hint = f" Parent directory {parent} does not exist." return _error(tool_use.id, f"File not found: {path}.{hint}") if path.is_dir(): items = sorted([f"{f.name}{'/' if f.is_dir() else ''}" for f in path.iterdir()]) return _ok(tool_use.id, f"Directory {path}:\n" + "\n".join(items)) try: content = path.read_text(encoding="utf-8") except UnicodeDecodeError: return _error(tool_use.id, f"File {path} is not UTF-8 text (may be binary).") lines = content.splitlines() view_range = input_.get("view_range") if view_range: start, end = view_range end = len(lines) if end == -1 else end lines = lines[start-1:end] offset = start else: offset = 1 numbered = "\n".join(f"{i+offset:6d}\t{line}" for i, line in enumerate(lines)) return _ok(tool_use.id, numbered) def _create(tool_use, path, input_): if path.exists(): return _error(tool_use.id, f"File already exists: {path}. Use str_replace to modify.") # Create parent dirs path.parent.mkdir(parents=True, exist_ok=True) path.write_text(input_["file_text"], encoding="utf-8") edit_history.setdefault(str(path), []).append(None) # None = did not exist return _ok(tool_use.id, f"Created {path} ({len(input_['file_text'])} chars)") def _str_replace(tool_use, path, input_): if not path.exists(): return _error(tool_use.id, f"File not found: {path}") content = path.read_text(encoding="utf-8") old = input_["old_str"] new = input_.get("new_str", "") count = content.count(old) if count == 0: return _error(tool_use.id, f"old_str not found in {path}. Verify exact text with view first.") if count > 1: return _error(tool_use.id, f"old_str appears {count} times in {path}. Include more context to make unique.") edit_history.setdefault(str(path), []).append(content) path.write_text(content.replace(old, new, 1), encoding="utf-8") return _ok(tool_use.id, f"Replaced 1 occurrence in {path}") def _insert(tool_use, path, input_): if not path.exists(): return _error(tool_use.id, f"File not found: {path}") content = path.read_text(encoding="utf-8") lines = content.splitlines(keepends=True) n = input_["insert_line"] if n < 0 or n > len(lines): return _error(tool_use.id, f"insert_line {n} out of range [0, {len(lines)}]") edit_history.setdefault(str(path), []).append(content) new_text = input_["new_str"] if not new_text.endswith("\n"): new_text += "\n" lines.insert(n, new_text) path.write_text("".join(lines), encoding="utf-8") return _ok(tool_use.id, f"Inserted after line {n} in {path}") def _undo(tool_use, path, input_): stack = edit_history.get(str(path), []) if not stack: return _error(tool_use.id, f"No edits recorded for {path}") previous = stack.pop() if previous is None: path.unlink() # file was created; remove it return _ok(tool_use.id, f"Undone: removed {path}") path.write_text(previous, encoding="utf-8") return _ok(tool_use.id, f"Undone: restored previous {path}")

3Provide Sandbox Location in System Prompt

Tell Claude where the sandbox root is

Include in your system prompt: "Your working directory is /workspace/. All file paths should be under /workspace/ (absolute paths preferred). The current project is at /workspace/my-project/." This dramatically reduces relative-path guessing errors.

Python · sandbox-aware system prompt
system_prompt = """You are a coding assistant with access to a Linux workspace. WORKSPACE: - Root: /workspace/ - Current project: /workspace/my-project/ - All file paths must be absolute paths under /workspace/ - The text_editor tool commands: view, create, str_replace, insert, undo_edit - Prefer view first to confirm file contents before edits - For str_replace, include enough context to make old_str unique BASH: - Working directory is /workspace/my-project/ - Long commands (npm install, tests) run with generous timeouts """ response = client.beta.messages.create( model="claude-sonnet-4-5", system=system_prompt, tools=[bash_tool, text_editor_tool], messages=messages, betas=["computer-use-2025-01-24"] )

Preventing This Error Going Forward

  • Always resolve to absolute paths. Relative → sandbox_root / relative.
  • Reject path traversal explicitly. Test resolved.relative_to(SANDBOX_ROOT).
  • Suggest alternatives in errors. difflib.get_close_matches is your friend.
  • Include sandbox location in system prompt. Reduces relative-path errors 80%.
  • Auto-create parent dirs on create. Reduces multi-step file creation.
  • Track edit history per file. Real undo_edit support.
  • Return descriptive errors, not raw exceptions. Claude self-corrects from good messages.
AR
Tested by Ahmed R.
Researcher · AI Error Hub
Last verified: Nov 15, 2026 · Tool version: text_editor_20250124

Frequently Asked Questions

Anthropic's tool schema includes it, so Claude may call it. Even a stub that returns "undo not supported in this environment" is fine — just don't crash. If you skip it, Claude may retry more edits than needed, wasting tokens.

Yes. Claude uses line numbers to reason about insert operations. The reference implementation returns them in NNNNNN\ttext format (6-digit right-aligned + tab). Consistent formatting helps Claude parse.

Yes. For view, cap at ~50KB — long files should be viewed in ranges. Return "File too large (X bytes); use view_range to see sections." For create, cap at ~500KB. For edits on massive files, refuse and suggest smaller operations.

Path.resolve() follows symlinks. If the resolved target is outside sandbox, reject. Safer than allowing symlinks. If you need symlinks (rare), audit each carefully.

Not usually needed — Claude edits sequentially, one call at a time. If your executor is concurrent (parallel agents), use file locks. Simplest: mutex per-path.