How to Implement File Editing Using the str_replace Tool in Codebuff

The str_replace tool in Codebuff applies exact-match string replacements to files and returns a unified diff, enabling programmatic file editing through JSON payloads or the TypeScript SDK.

To implement file editing using the str_replace tool in Codebuff, you need to understand its architecture across the monorepo. The tool is defined in the common package, handled by the agent-runtime, and consumed by the CLI or custom agents. It performs exact-match replacements with optional indentation handling and generates unified diffs for verification.

Understanding the str_replace Tool Architecture

Core Components and File Locations

The implementation spans several packages in the Codebuff repository:

Data Flow Overview

When you implement file editing using the str_replace tool in Codebuff, the data flows through these stages:

  1. Invocation – An agent or script creates a tool call with cb_tool_name: "str_replace" and an input payload matching the Zod schema.

  2. Handling – The runtime routes the call to handleStrReplace, which fetches the current file content (or pending edits) and calls processStrReplace.

  3. ProcessingprocessStrReplace normalizes line endings, iterates over each replacement entry, and applies exact-match logic. If allowMultiple is false and multiple matches exist, it returns an error. If indentation differs, it attempts fuzzy matching via tryToDoStringReplacementWithExtraIndentation.

  4. Diff Generation – The processor creates a unified diff using createPatch, stripping headers to return only the hunks.

  5. Response – The handler streams the result back, including the new file content, success message, and unifiedDiff string.

Implementing str_replace in Your Code

Basic JSON Payload Structure

To invoke the tool directly, construct a JSON block following the schema defined in common/src/tools/params/tool/str-replace.ts:

{
  "cb_tool_name": "str_replace",
  "input": {
    "path": "src/utils/logger.ts",
    "replacements": [
      {
        "old": "export const logger = createLogger();",
        "new": "export const logger = createLogger({ level: 'debug' });",
        "allowMultiple": false
      }
    ]
  }
}

The allowMultiple parameter controls whether to replace all occurrences (true) or enforce a single match (false). When false, the tool returns an error if the old string appears more than once, preventing accidental mass replacements.

TypeScript SDK Implementation

For programmatic access within the Codebuff ecosystem, use the SDK's invokeTool function:

import { invokeTool } from '@codebuff/sdk';

const toolCall = {
  cb_tool_name: 'str_replace' as const,
  input: {
    path: 'src/util/logger.ts',
    replacements: [
      {
        old: 'export const logger = createLogger();',
        new: 'export const logger = createLogger({ level: "debug" });',
        allowMultiple: false,
      },
    ],
  },
};

const result = await invokeTool(toolCall);
console.log(result.unifiedDiff);

This approach routes through the runtime's handleStrReplace handler, ensuring proper file locking, diff generation, and error handling.

Previewing Changes with propose_str_replace

Before committing changes, use propose_str_replace to generate a diff without writing to disk:

{
  "cb_tool_name": "propose_str_replace",
  "input": {
    "path": "src/api.ts",
    "replacements": [
      {
        "old": "export function fetchData() {",
        "new": "export async function fetchData() {",
        "allowMultiple": false
      }
    ]
  }
}

The response includes unifiedDiff but leaves the filesystem untouched. After review, execute the identical payload with str_replace to apply the edit.

Handling Edge Cases and Validation

The processStrReplace function in packages/agent-runtime/src/process-str-replace.ts includes robust handling for common editing pitfalls:

  • Line ending normalization – Converts Windows \r\n to Unix \n internally, then restores the original line ending style in the output (lines 40-42).

  • Indentation tolerance – When exact matches fail due to whitespace differences, tryToDoStringReplacementWithExtraIndentation attempts to align the replacement by adding or removing leading spaces (lines 52-60).

  • Multiple occurrence protection – Unless allowMultiple is explicitly true, the tool errors if the search string appears more than once, preventing unintended global replacements (lines 41-46).

  • No-op detection – If replacements result in identical content, the tool returns a "No change to the file" error rather than writing redundant data (lines 84-98).

Rendering Diffs for User Interface

To display edits in a CLI timeline or custom interface, use the helper from cli/src/utils/implementor-helpers.ts:

import { constructDiffFromReplacements } from '../../cli/src/utils/implementor-helpers';

const diff = constructDiffFromReplacements([
  { old: 'const x = 1;', new: 'const x = 2;' },
  { old: 'console.log("debug");', new: '' },
]);

console.log(diff);

This generates a Git-style diff view showing removed lines with - and added lines with +, suitable for terminal output or web rendering.

Summary

Frequently Asked Questions

What is the difference between str_replace and propose_str_replace in Codebuff?

The str_replace tool writes changes directly to the filesystem and returns a unified diff, while propose_str_replace performs the same validation and diff generation without modifying the file. Use propose_str_replace when you need user approval or want to preview changes in a multi-step workflow before committing them.

How does str_replace handle whitespace and indentation differences?

According to the source code in packages/agent-runtime/src/process-str-replace.ts, the tool first attempts an exact match including whitespace. If that fails, it calls tryToDoStringReplacementWithExtraIndentation to adjust for leading spaces or tabs, allowing the replacement to succeed even when the indentation level differs between the search string and the actual file content.

Can str_replace perform multiple replacements in a single file?

Yes, the replacements array in the input payload accepts multiple objects, each with old, new, and optional allowMultiple properties. However, for each individual replacement, allowMultiple controls whether that specific string can be replaced multiple times within the file. Setting it to false enforces a single match and returns an error if duplicates are found, preventing accidental mass replacements.

What happens if the replacement does not change the file content?

The processStrReplace function explicitly checks for no-op edits. If the new content is identical to the original after processing all replacements, it returns an error with the message "No change to the file" rather than writing redundant data to disk. This prevents empty commits and unnecessary file system operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →