|cat -n format| Display[LLM Sees] Display -->|Strips line numbers| Edit[EditTool] Edit --> Validate{Validation} Validate -->|Pass| Apply[Apply Edit] Validate -->|Fail| Error[Error Result] Apply --> Cache[Update Cache] Cache --> Diff[Generate Diff] Diff --> Confirm[Confirmation] subgraph "Validation Checks" V1[File was read?] V2[File unchanged?] V3[String exists?] V4[Count matches?] V5[Not no-op?] end Validate --> V1 V1 --> V2 V2 --> V3 V3 --> V4 V4 --> V5 end "> |cat -n format| Display[LLM Sees] Display -->|Strips line numbers| Edit[EditTool] Edit --> Validate{Validation} Validate -->|Pass| Apply[Apply Edit] Validate -->|Fail| Error[Error Result] Apply --> Cache[Update Cache] Cache --> Diff[Generate Diff] Diff --> Confirm[Confirmation] subgraph "Validation Checks" V1[File was read?] V2[File unchanged?] V3[String exists?] V4[Count matches?] V5[Not no-op?] end Validate --> V1 V1 --> V2 V2 --> V3 V3 --> V4 V4 --> V5 end "> |cat -n format| Display[LLM Sees] Display -->|Strips line numbers| Edit[EditTool] Edit --> Validate{Validation} Validate -->|Pass| Apply[Apply Edit] Validate -->|Fail| Error[Error Result] Apply --> Cache[Update Cache] Cache --> Diff[Generate Diff] Diff --> Confirm[Confirmation] subgraph "Validation Checks" V1[File was read?] V2[File unchanged?] V3[String exists?] V4[Count matches?] V5[Not no-op?] end Validate --> V1 V1 --> V2 V2 --> V3 V3 --> V4 V4 --> V5 end ">
graph TB
    subgraph "File Editing Pipeline"
        Read[ReadTool] -->|cat -n format| Display[LLM Sees]
        Display -->|Strips line numbers| Edit[EditTool]

        Edit --> Validate{Validation}
        Validate -->|Pass| Apply[Apply Edit]
        Validate -->|Fail| Error[Error Result]

        Apply --> Cache[Update Cache]
        Cache --> Diff[Generate Diff]
        Diff --> Confirm[Confirmation]

        subgraph "Validation Checks"
            V1[File was read?]
            V2[File unchanged?]
            V3[String exists?]
            V4[Count matches?]
            V5[Not no-op?]
        end

        Validate --> V1
        V1 --> V2
        V2 --> V3
        V3 --> V4
        V4 --> V5
    end

The File Editing Pipeline Architecture

File editing in Claude Code isn't just about changing text—it's a carefully orchestrated pipeline designed to handle the complexities of AI-assisted code modification:

class FileEditingPipeline {
  // The four-phase editing cycle
  static async executeEdit(
    tool: EditTool,
    input: EditInput,
    context: ToolContext
  ): Promise<EditResult> {
    // Phase 1: Validation
    const validation = await this.validateEdit(input, context);
    if (!validation.valid) {
      return { success: false, error: validation.error };
    }

    // Phase 2: Preparation
    const prepared = await this.prepareEdit(input, validation.fileState);

    // Phase 3: Application
    const result = await this.applyEdit(prepared);

    // Phase 4: Verification
    const verified = await this.verifyEdit(result, input);

    return verified;
  }

  // The state tracking system
  private static fileStates = new Map<string, FileState>();

  interface FileState {
    content: string;
    hash: string;
    mtime: number;
    encoding: BufferEncoding;
    lineEndings: '\\\\n' | '\\\\r\\\\n' | '\\\\r';
    isBinary: boolean;
    size: number;
  }
}

Why Multiple Tools Instead of One Universal Editor?

Tool Purpose Guarantees Failure Mode
EditTool Single string replacement Exact match count Fails if occurrence ≠ expected
MultiEditTool Sequential edits Atomic batch Fails if any edit invalid
WriteTool Full replacement Complete overwrite Fails if not read first
NotebookEditTool Cell operations Structure preserved Fails if cell missing

Each tool provides specific guarantees that a universal editor couldn't maintain while remaining LLM-friendly.

The Line Number Problem: A Deceptively Complex Challenge

The most critical challenge in file editing is the line number prefix problem:

// What the LLM sees from ReadTool:
const readOutput = `
1	function hello() {
2	  console.log('Hello, world!');
3	}
`;

// What the LLM might incorrectly try to edit:
const wrongOldString = "2	  console.log('Hello, world!');";  // WRONG - includes line number

// What it should use:
const correctOldString = "  console.log('Hello, world!');";  // CORRECT - no line number

The line number stripping logic:

class LineNumberHandler {
  // The LLM receives extensive instructions about this
  static readonly LINE_NUMBER_PATTERN = /^\\\\d+\\\\t/;

  static stripLineNumbers(content: string): string {
    return content
      .split('\\\\n')
      .map(line => line.replace(this.LINE_NUMBER_PATTERN, ''))
      .join('\\\\n');
  }

  // But the real challenge is ensuring the LLM does this
  static validateOldString(
    oldString: string,
    fileContent: string
  ): ValidationResult {
    // Check 1: Does oldString contain line number prefix?
    if (this.LINE_NUMBER_PATTERN.test(oldString)) {
      return {
        valid: false,
        error: 'old_string appears to contain line number prefix. ' +
               'Remove the number and tab at the start.',
        suggestion: oldString.replace(this.LINE_NUMBER_PATTERN, '')
      };
    }

    // Check 2: Does the string exist in the file?
    const occurrences = this.countOccurrences(fileContent, oldString);
    if (occurrences === 0) {
      // Try to detect if it's a line number issue
      const possibleLineNumber = oldString.match(/^(\\\\d+)\\\\t/);
      if (possibleLineNumber) {
        const lineNum = parseInt(possibleLineNumber[1]);
        const actualLine = this.getLine(fileContent, lineNum);
        return {
          valid: false,
          error: `String not found. Did you include line number ${lineNum}?`,
          suggestion: actualLine
        };
      }
    }

    return { valid: true, occurrences };
  }
}

EditTool: Surgical Precision in String Replacement

The EditTool implements exact string matching with zero ambiguity:

class EditToolImplementation {
  static async executeEdit(
    input: EditInput,
    context: ToolContext
  ): Promise<EditResult> {
    const { file_path, old_string, new_string, expected_replacements = 1 } = input;

    // Step 1: Retrieve cached file state
    const cachedFile = context.readFileState.get(file_path);
    if (!cachedFile) {
      throw new Error(
        'File must be read with ReadFileTool before editing. ' +
        'This ensures you have the current file content.'
      );
    }

    // Step 2: Verify file hasn't changed externally
    const currentStats = await fs.stat(file_path);
    if (currentStats.mtimeMs !== cachedFile.timestamp) {
      throw new Error(
        'File has been modified externally since last read. ' +
        'Please read the file again to see current content.'
      );
    }

    // Step 3: Validate the edit
    const validation = this.validateEdit(
      old_string,
      new_string,
      cachedFile.content,
      expected_replacements
    );

    if (!validation.valid) {
      throw new Error(validation.error);
    }

    // Step 4: Apply the replacement
    const newContent = this.performReplacement(
      cachedFile.content,
      old_string,
      new_string,
      expected_replacements
    );

    // Step 5: Generate diff for verification
    const diff = this.generateDiff(
      cachedFile.content,
      newContent,
      file_path
    );

    // Step 6: Write with same encoding/line endings
    await this.writeFilePreservingFormat(
      file_path,
      newContent,
      cachedFile
    );

    // Step 7: Update cache
    context.readFileState.set(file_path, {
      content: newContent,
      timestamp: Date.now()
    });

    // Step 8: Generate context snippet
    const snippet = this.generateContextSnippet(
      newContent,
      new_string,
      5 // lines of context
    );

    return {
      success: true,
      diff,
      snippet,
      replacements: expected_replacements
    };
  }

  private static validateEdit(
    oldString: string,
    newString: string,
    fileContent: string,
    expectedReplacements: number
  ): EditValidation {
    // No-op check
    if (oldString === newString) {
      return {
        valid: false,
        error: 'old_string and new_string are identical. No changes would be made.'
      };
    }

    // Empty old_string special case (insertion)
    if (oldString === '') {
      return {
        valid: false,
        error: 'Empty old_string not allowed. Use WriteTool for new files.'
      };
    }

    // Count occurrences with exact string matching
    const occurrences = this.countExactOccurrences(fileContent, oldString);

    if (occurrences === 0) {
      return {
        valid: false,
        error: 'old_string not found in file. Ensure exact match including whitespace.',
        suggestion: this.findSimilarStrings(fileContent, oldString)
      };
    }

    if (occurrences !== expectedReplacements) {
      return {
        valid: false,
        error: `Expected ${expectedReplacements} replacement(s) but found ${occurrences} occurrence(s). ` +
               `Set expected_replacements to ${occurrences} or refine old_string.`
      };
    }

    return { valid: true };
  }

  private static countExactOccurrences(
    content: string,
    searchString: string
  ): number {
    // Escape special regex characters for exact matching
    const escaped = searchString.replace(/[.*+?^${}()|[\\\\]\\\\\\\\]/g, '\\\\\\\\$&');
    const regex = new RegExp(escaped, 'g');
    return (content.match(regex) || []).length;
  }

  private static performReplacement(
    content: string,
    oldString: string,
    newString: string,
    limit: number
  ): string {
    // Character escaping for special replacement patterns
    const escapeReplacement = (str: string) => {
      return str
        .replace(/\\\\$/g, '$$$$')  // $ -> $$
        .replace(/\\\\n/g, '\\\\n')    // Preserve newlines
        .replace(/\\\\r/g, '\\\\r');   // Preserve carriage returns
    };

    const escapedNew = escapeReplacement(newString);

    let result = content;
    let count = 0;
    let lastIndex = 0;

    // Manual replacement to respect limit
    while (count < limit) {
      const index = result.indexOf(oldString, lastIndex);
      if (index === -1) break;

      result = result.slice(0, index) +
               newString +  // Use original, not escaped
               result.slice(index + oldString.length);

      lastIndex = index + newString.length;
      count++;
    }

    return result;
  }

  private static generateDiff(
    oldContent: string,
    newContent: string,
    filePath: string
  ): string {
    // Use unified diff format
    const diff = createUnifiedDiff(
      filePath,
      filePath,
      oldContent,
      newContent,
      'before edit',
      'after edit',
      { context: 3 }
    );

    return diff;
  }
}

Why expected_replacements Matters:

// Scenario: Multiple occurrences
const fileContent = `
function processUser(user) {
  console.log(user);
  return user;
}
`;

// Without expected_replacements:
edit({
  old_string: "user",
  new_string: "userData"
});
// Result: ALL occurrences replaced (function parameter too!)

// With expected_replacements:
edit({
  old_string: "user",
  new_string: "userData",
  expected_replacements: 2  // Only the uses, not parameter
});
// Result: Fails - forces more specific old_string

MultiEditTool: Atomic Sequential Operations

MultiEditTool solves the complex problem of multiple related edits:

class MultiEditToolImplementation {
  static async executeMultiEdit(
    input: MultiEditInput,
    context: ToolContext
  ): Promise<MultiEditResult> {
    const { file_path, edits } = input;

    // Load file once
    const cachedFile = context.readFileState.get(file_path);
    if (!cachedFile) {
      throw new Error('File must be read before editing');
    }

    // Validate all edits before applying any
    const validationResult = this.validateAllEdits(
      edits,
      cachedFile.content
    );

    if (!validationResult.valid) {
      throw new Error(validationResult.error);
    }

    // Apply edits sequentially to working copy
    let workingContent = cachedFile.content;
    const appliedEdits: AppliedEdit[] = [];

    for (let i = 0; i < edits.length; i++) {
      const edit = edits[i];

      try {
        // Validate this edit against current working content
        const validation = this.validateSingleEdit(
          edit,
          workingContent,
          i
        );

        if (!validation.valid) {
          throw new Error(
            `Edit ${i + 1} failed: ${validation.error}`
          );
        }

        // Apply edit
        const beforeEdit = workingContent;
        workingContent = this.applyEdit(
          workingContent,
          edit
        );

        appliedEdits.push({
          index: i,
          edit,
          diff: this.generateEditDiff(beforeEdit, workingContent),
          summary: this.summarizeEdit(edit)
        });

      } catch (error) {
        // Atomic failure - no changes written
        throw new Error(
          `MultiEdit aborted at edit ${i + 1}/${edits.length}: ${error.message}`
        );
      }
    }

    // All edits validated and applied - write once
    await this.writeFilePreservingFormat(
      file_path,
      workingContent,
      cachedFile
    );

    // Update cache
    context.readFileState.set(file_path, {
      content: workingContent,
      timestamp: Date.now()
    });

    return {
      success: true,
      editsApplied: appliedEdits,
      totalDiff: this.generateDiff(
        cachedFile.content,
        workingContent,
        file_path
      )
    };
  }

  private static validateAllEdits(
    edits: Edit[],
    originalContent: string
  ): ValidationResult {
    // Check for empty edits array
    if (edits.length === 0) {
      return {
        valid: false,
        error: 'No edits provided'
      };
    }

    // Detect potential conflicts
    const conflicts = this.detectEditConflicts(edits, originalContent);
    if (conflicts.length > 0) {
      return {
        valid: false,
        error: 'Edit conflicts detected:\\\\n' +
               conflicts.map(c => c.description).join('\\\\n')
      };
    }

    // Simulate all edits to ensure they work
    let simulatedContent = originalContent;
    for (let i = 0; i < edits.length; i++) {
      const edit = edits[i];
      const occurrences = this.countOccurrences(
        simulatedContent,
        edit.old_string
      );

      if (occurrences === 0) {
        return {
          valid: false,
          error: `Edit ${i + 1}: old_string not found. ` +
                 `Previous edits may have removed it.`
        };
      }

      if (occurrences !== (edit.expected_replacements || 1)) {
        return {
          valid: false,
          error: `Edit ${i + 1}: Expected ${edit.expected_replacements || 1} ` +
                 `replacements but found ${occurrences}`
        };
      }

      // Apply to simulation
      simulatedContent = this.applyEdit(simulatedContent, edit);
    }

    return { valid: true };
  }

  private static detectEditConflicts(
    edits: Edit[],
    content: string
  ): EditConflict[] {
    const conflicts: EditConflict[] = [];

    for (let i = 0; i < edits.length - 1; i++) {
      for (let j = i + 1; j < edits.length; j++) {
        const edit1 = edits[i];
        const edit2 = edits[j];

        // Conflict Type 1: Later edit modifies earlier edit's result
        if (edit2.old_string.includes(edit1.new_string)) {
          conflicts.push({
            type: 'dependency',
            edits: [i, j],
            description: `Edit ${j + 1} depends on result of edit ${i + 1}`
          });
        }

        // Conflict Type 2: Overlapping replacements
        if (this.editsOverlap(edit1, edit2, content)) {
          conflicts.push({
            type: 'overlap',
            edits: [i, j],
            description: `Edits ${i + 1} and ${j + 1} affect overlapping text`
          });
        }

        // Conflict Type 3: Same target, different replacements
        if (edit1.old_string === edit2.old_string &&
            edit1.new_string !== edit2.new_string) {
          conflicts.push({
            type: 'contradiction',
            edits: [i, j],
            description: `Edits ${i + 1} and ${j + 1} replace same text differently`
          });
        }
      }
    }

    return conflicts;
  }

  private static editsOverlap(
    edit1: Edit,
    edit2: Edit,
    content: string
  ): boolean {
    // Find positions of all occurrences
    const positions1 = this.findAllPositions(content, edit1.old_string);
    const positions2 = this.findAllPositions(content, edit2.old_string);

    // Check if any positions overlap
    for (const pos1 of positions1) {
      const end1 = pos1 + edit1.old_string.length;

      for (const pos2 of positions2) {
        const end2 = pos2 + edit2.old_string.length;

        // Check for overlap
        if (pos1 < end2 && pos2 < end1) {
          return true;
        }
      }
    }

    return false;
  }
}