# fs_read vs fs_patch in Forge: When to Use Each File System Tool

> Understand fs_read vs fs_patch in Forge. Use fs_read for inspecting files and fs_patch for safe, validated edits with automatic snapshots. Learn which tool fits your needs.

- Repository: [Forge Code/forgecode](https://github.com/antinomyhq/forgecode)
- Tags: how-to-guide
- Published: 2026-04-08

---

**Use `fs_read` to inspect file contents without modification and `fs_patch` to apply controlled edits with automatic snapshots and validation.**

Forge, the AI coding assistant from the `antinomyhq/forgecode` repository, provides two primary file system tools for LLM interactions: `fs_read` for safe file inspection and `fs_patch` for controlled modifications. Understanding the architectural differences between these services ensures you choose the right tool for reading source code versus applying programmatic edits.

## Core Architectural Differences

The tools operate through distinct service traits with separate responsibilities in the Forge engine.

### FsReadService Implementation

According to [`forge_app/src/services.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_app/src/services.rs), the **FsReadService** trait defines read-only operations. The concrete implementation `ForgeFsRead<F>` in [`forge_services/src/tool_services/fs_read.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_read.rs) handles MIME type detection, enforces size limits, and returns a **ReadOutput** struct containing either `Content::text` or `Content::image` alongside file metadata. This service guarantees immutability—no file system modifications occur during execution.

### FsPatchService Implementation

The **FsPatchService** trait, also defined in [`forge_app/src/services.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_app/src/services.rs), powers file modifications through `ForgeFsPatch<F>` in [`forge_services/src/tool_services/fs_patch.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_patch.rs). Unlike the read service, this implementation creates snapshots for undo functionality, computes edit ranges using exact or fuzzy string matching, and runs remote validation before finalizing writes. It returns a **PatchOutput** containing both the original and modified content states.

## When to Use fs_read for File Inspection

Use `fs_read` whenever you need to display file contents to the LLM without altering the underlying data. According to the source code in [`forge_services/src/tool_services/fs_read.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_read.rs), the service automatically enforces `max_file_size_bytes` and `max_image_size_bytes` limits, rejects binary files exceeding visual thresholds, and truncates output to `max_read_lines` (defaulting to 2,000 lines).

Typical use cases include:
- Showing source code to the LLM for analysis or code review
- Displaying image or PDF previews inside chat contexts
- Fetching specific line ranges (e.g., lines 10-20) for targeted discussion

## When to Use fs_patch for File Modifications

Use `fs_patch` when you need to programmatically change file contents. The implementation in [`forge_services/src/tool_services/fs_patch.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_patch.rs) supports single edits, batch operations via `multi_patch`, and operations like `replace`, `append`, and `prepend`. Before writing changes, the service stores a snapshot enabling rollback and runs remote validation to ensure file integrity.

Typical use cases include:
- Fixing bugs automatically by replacing deprecated API calls
- Adding imports or comments to specific locations
- Performing batch refactors across multiple locations in a single file

## Code Examples

### Reading Files with FsReadService

The `read` method accepts optional start and end line parameters to limit output size:

```rust
use forge_app::{FsReadService, ReadOutput};

async fn demo_read<S: FsReadService>(services: &S) -> anyhow::Result<()> {
    // Read the first 50 lines of `src/main.rs`
    let output: ReadOutput = services
        .read(
            "src/main.rs".to_string(),
            Some(1),          // start_line
            Some(50),         // end_line
        )
        .await?;
    
    println!("File hash: {}", output.info.hash());
    println!("Content:\n{}", output.content.as_text()?);
    Ok(())
}

```

*Source:* [`forge_services/src/tool_services/fs_read.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_read.rs) (lines 87-115, 162-170)

### Applying Single Edits with FsPatchService

The `patch` method performs string replacement with optional fuzzy matching when exact matches fail:

```rust
use forge_app::{FsPatchService, PatchOutput};

async fn demo_patch<S: FsPatchService>(services: &S) -> anyhow::Result<()> {
    let result: PatchOutput = services
        .patch(
            "src/lib.rs".to_string(),
            "old_api()".to_string(), // search string
            "new_api()".to_string(), // replacement
            false,                   // replace_all = false
        )
        .await?;
    
    println!("Before:\n{}", result.before);
    println!("After:\n{}", result.after);
    Ok(())
}

```

*Source:* [`forge_services/src/tool_services/fs_patch.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_patch.rs) (lines 31-42, 86-106)

### Batch Operations with multi_patch

For multiple edits in a single transaction, use `multi_patch` with a vector of `PatchEdit` structs:

```rust
use forge_domain::PatchEdit;
use forge_app::FsPatchService;

async fn demo_multi_patch<S: FsPatchService>(services: &S) -> anyhow::Result<()> {
    let edits = vec![
        PatchEdit {
            old_string: "use std::fmt;".to_string(),
            new_string: "use std::fmt::{self, Display};".to_string(),
            replace_all: false,
        },
        PatchEdit {
            old_string: "// TODO".to_string(),
            new_string: "// ✅ Fixed".to_string(),
            replace_all: true,
        },
    ];

    let output = services
        .multi_patch("src/main.rs".to_string(), edits)
        .await?;

    println!("Applied edits to {} lines", output.after.matches('\n').count() + 1);
    Ok(())
}

```

*Source:* [`forge_services/src/tool_services/fs_patch.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_patch.rs) (lines 15-19, 30-44)

## Safety Mechanisms and Validation

Both tools implement safety guards appropriate to their risk profiles.

**FsRead** enforces read-only constraints through size limits and line truncation. The service in [`forge_services/src/tool_services/fs_read.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_read.rs) detects MIME types to handle text versus visual content differently, ensuring binary files cannot overwhelm the LLM context window.

**FsPatch** implements write-safety through snapshot creation and validation. Before any modification, the service creates a recoverable snapshot enabling undo operations. After applying edits, it runs remote validation via the validation API (with graceful fallback) and returns any errors in the `PatchOutput.errors` vector without necessarily failing the entire operation.

## Summary

- **Use `fs_read`** (via `FsReadService`) for immutable file inspection, leveraging MIME detection and configurable line limits to safely present content to the LLM.
- **Use `fs_patch`** (via `FsPatchService`) for mutable operations, relying on automatic snapshots, fuzzy search capabilities, and remote validation to ensure safe, reversible edits.
- **Reference [`forge_app/src/operation.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_app/src/operation.rs)** for the dispatch logic that routes `ToolOperation::FsRead` and `ToolOperation::FsPatch` to their respective service implementations.
- **Consider `multi_patch`** for batch operations to minimize filesystem round-trips and ensure atomic edit application.

## Frequently Asked Questions

### Can `fs_patch` read files without modifying them?

No. While `fs_patch` internally reads the original file to compute the `before` state for the `PatchOutput`, its primary purpose is modification. For read-only operations, use `fs_read` (via `FsReadService`) which enforces stricter size limits and provides optimized text or image content handling without write-side overhead.

### What happens if `fs_patch` cannot find the exact search string?

According to the implementation in [`forge_services/src/tool_services/fs_patch.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_patch.rs), the service falls back to **fuzzy search** when an exact match is not found. This allows edits to succeed even with minor whitespace or formatting differences. If fuzzy matching still fails, the operation returns an error without modifying the file, preserving data integrity.

### How does Forge handle large files when using `fs_read`?

The `fs_read` implementation enforces three layers of protection: `max_file_size_bytes` for general files, `max_image_size_bytes` for visual content, and `max_read_lines` (default 2,000) for text truncation. Binary files exceeding visual limits are rejected, while text files are truncated to prevent LLM context overflow, as defined in [`forge_services/src/tool_services/fs_read.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_read.rs).

### Is it possible to undo changes made by `fs_patch`?

Yes. The `FsPatchService` automatically creates a **snapshot** before writing any modifications, storing the original content for undo operations. While the API client manages the undo lifecycle, the underlying infrastructure in [`forge_services/src/tool_services/fs_patch.rs`](https://github.com/antinomyhq/forgecode/blob/main/forge_services/src/tool_services/fs_patch.rs) guarantees that every `patch` or `multi_patch` operation retains the pre-edit state for recovery.