> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gravixlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Search and Replace

> Rewrite a pattern across every matching file in a runtime directory tree

`POST /v1/agents/runtime/{runtime_id}/files/replace`

Replaces every occurrence of a pattern across the files selected by `path` and `glob`. Like
[Find Files](/documentation/agentruntime/file-operations/find-files), the rewrite runs
natively in the guest agent: there is no `sed`, no shell and no command injection surface.

Each file is rewritten atomically. The new contents are written to a sibling temporary file,
flushed to disk, given the original mode, owner and group, and then renamed over the
original. A reader either sees the whole old file or the whole new file, never a partial
one, and a crash mid-run cannot leave a truncated source file behind.

<CodeGroup>
  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  from gravixlayer import GravixLayer

  client = GravixLayer()  # defaults to cloud="aws", region="us-east-1"
  sandbox = client.runtime.create()  # defaults to template="base-small"

  sandbox.file.write("/workspace/app.py", 'DB = "localhost:5432"\n')
  sandbox.file.write("/workspace/pyproject.toml", 'version = "1.2.3"\n')

  preview = sandbox.file.replace(
      "/workspace", "localhost:5432", "db.internal:5432",
      glob="*.py", dry_run=True,
  )
  for entry in preview:
      print(entry.path, entry.replacements)

  # Then apply
  result = sandbox.file.replace(
      "/workspace", "localhost:5432", "db.internal:5432", glob="*.py",
  )
  print(result.total_replacements, "across", len(result), "files")

  # Regular expression with capture groups
  sandbox.file.replace(
      "/workspace",
      r"version\s*=\s*\"([0-9.]+)\"",
      'version = "2.0.0"',
      glob="*.toml",
      regex=True,
  )

  sandbox.kill()
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import { GravixLayer } from 'gravixlayer';

  const client = new GravixLayer(); // defaults to cloud="aws", region="us-east-1"
  const sandbox = await client.runtime.create(); // defaults to template="base-small"

  await sandbox.file.write('/workspace/app.py', 'DB = "localhost:5432"\n');
  await sandbox.file.write('/workspace/pyproject.toml', 'version = "1.2.3"\n');

  const preview = await sandbox.file.replace('/workspace', 'localhost:5432', 'db.internal:5432', {
    glob: '*.py',
    dryRun: true,
  });
  for (const entry of preview.files) {
    console.log(entry.path, entry.replacements);
  }

  const result = await sandbox.file.replace('/workspace', 'localhost:5432', 'db.internal:5432', {
    glob: '*.py',
  });
  console.log(result.totalReplacements, 'across', result.files.length, 'files');

  await sandbox.file.replace(
    '/workspace',
    String.raw`version\s*=\s*"([0-9.]+)"`,
    'version = "2.0.0"',
    { glob: '*.toml', regex: true },
  );

  await sandbox.kill();
  ```

  ```bash CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  gravixlayer runtime files replace "$RT" /workspace "localhost:5432" "db.internal:5432" \
    --glob "*.py" --dry-run
  gravixlayer runtime files replace "$RT" /workspace "localhost:5432" "db.internal:5432" \
    --glob "*.py"
  ```

  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.gravixlayer.ai/v1/agents/runtime/$RT/files/replace" \
    -H "Authorization: Bearer $GRAVIXLAYER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"path":"/workspace","pattern":"localhost:5432","replacement":"db.internal:5432","glob":"*.py"}'
  ```
</CodeGroup>

## Parameters

| Parameter        | Type    | Required | Description                                                                                                |
| ---------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `path`           | string  | Yes      | Absolute directory to rewrite. A file path rewrites just that file                                         |
| `pattern`        | string  | Yes      | Text to replace. Must not be empty                                                                         |
| `replacement`    | string  | Yes      | Text to substitute. May be empty to delete matches                                                         |
| `glob`           | string  | No       | Shell style name pattern such as `*.py`, limiting which files are rewritten                                |
| `regex`          | boolean | No       | Treat `pattern` as a regular expression and expand `$1` style groups in `replacement`. Defaults to `false` |
| `case_sensitive` | boolean | No       | Match case exactly. Defaults to `false`                                                                    |
| `include_hidden` | boolean | No       | Descend into and rewrite dot-files. Defaults to `false`                                                    |
| `max_depth`      | integer | No       | Directory recursion limit. Defaults to 64, capped at 256                                                   |
| `dry_run`        | boolean | No       | Count replacements without writing anything. Defaults to `false`                                           |

## Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "success": true,
  "files": [
    {"path": "/workspace/app.py", "replacements": 3},
    {"path": "/workspace/db/pool.py", "replacements": 1}
  ],
  "total_replacements": 4,
  "files_scanned": 137,
  "dry_run": false
}
```

| Field                  | Type    | Description                                              |
| ---------------------- | ------- | -------------------------------------------------------- |
| `files[].path`         | string  | Absolute path of a rewritten file                        |
| `files[].replacements` | integer | Occurrences replaced in that file                        |
| `total_replacements`   | integer | Sum across every file                                    |
| `files_scanned`        | integer | Number of files examined                                 |
| `dry_run`              | boolean | Echoes the request flag; when `true` nothing was written |

Files with zero occurrences are not listed and are not rewritten, so their mtime is
untouched.

## Behaviour notes

* **Literal by default.** Without `regex`, both the pattern and the replacement are treated
  as plain text: `$1` in the replacement stays `$1`, and no escaping is required.
* **Capture groups need `regex`.** With `regex: true`, `$1`, `$2` and `${name}` in the
  replacement expand to the corresponding capture groups.
* **Metadata is preserved.** Mode bits, owner and group are restored on the rewritten file,
  so a replace across a tree does not silently change permissions or ownership.
* **Binary and oversized files are skipped**, using the same rules as Find Files: NUL byte
  sniff, UTF-8 validation and an 8 MiB ceiling.
* **Directory symlinks are never followed**, so a link cannot be used to rewrite files
  outside `path`.
* **Dry run first for wide patterns.** `dry_run` gives you the exact per-file counts the
  real run would produce, without touching the filesystem.
