> ## 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.

# Find Files

> Search a runtime directory tree by name glob and file contents

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

Walks a directory tree inside the runtime and returns the files, and the individual lines,
that match. The search executes natively in the guest agent: no shell is invoked and no
`find`, `grep` or `ripgrep` process is spawned, so there is nothing to quote, escape or
sanitise and no command injection surface.

Supply `glob` to choose which files are considered, `pattern` to match their contents, or
both together.

<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", "# TODO: retry\nfrom os import path\n")
  sandbox.file.write("/workspace/config.yaml", "name: demo\n")

  hits = sandbox.file.find("/workspace", pattern="TODO", glob="*.py")
  for match in hits:
      print(f"{match.path}:{match.line}: {match.content}")

  # Name-only search: one result per file, line == 0
  configs = sandbox.file.find("/workspace", glob="*.yaml")
  print([m.path for m in configs])

  # Regular expression search
  imports = sandbox.file.find(
      "/workspace",
      pattern=r"^from\s+(\w+)\s+import",
      glob="*.py",
      regex=True,
  )
  print(imports.files_scanned, len(imports))

  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', '# TODO: retry\nfrom os import path\n');
  await sandbox.file.write('/workspace/config.yaml', 'name: demo\n');

  const hits = await sandbox.file.find('/workspace', { pattern: 'TODO', glob: '*.py' });
  for (const match of hits.matches) {
    console.log(`${match.path}:${match.line}: ${match.content}`);
  }

  const configs = await sandbox.file.find('/workspace', { glob: '*.yaml' });
  console.log(configs.matches.map((m) => m.path));

  const imports = await sandbox.file.find('/workspace', {
    pattern: String.raw`^from\s+(\w+)\s+import`,
    glob: '*.py',
    regex: true,
  });
  console.log(imports.filesScanned, imports.matches.length);

  await sandbox.kill();
  ```

  ```bash CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  gravixlayer runtime files find "$RT" /workspace --pattern TODO --glob "*.py"
  gravixlayer runtime files find "$RT" /workspace --glob "*.yaml"
  gravixlayer runtime files find "$RT" /workspace --pattern '^import ' --regex --max-results 50
  ```

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

## Parameters

| Parameter        | Type    | Required    | Description                                                                                          |
| ---------------- | ------- | ----------- | ---------------------------------------------------------------------------------------------------- |
| `path`           | string  | Yes         | Absolute directory to search. A file path searches just that file                                    |
| `pattern`        | string  | Conditional | Text to match inside each file. Required unless `glob` is given                                      |
| `glob`           | string  | Conditional | Shell style name pattern such as `*.py`. Required unless `pattern` is given                          |
| `regex`          | boolean | No          | Treat `pattern` as a regular expression. Defaults to `false`, meaning `pattern` is matched literally |
| `case_sensitive` | boolean | No          | Match case exactly. Defaults to `false`                                                              |
| `include_hidden` | boolean | No          | Descend into and match dot-files. Defaults to `false`                                                |
| `max_results`    | integer | No          | Stop after this many matches. Defaults to 1000, capped at 10000                                      |
| `max_depth`      | integer | No          | Directory recursion limit. Defaults to 64, capped at 256                                             |

## Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "success": true,
  "matches": [
    {"path": "/workspace/app.py", "line": 42, "column": 5, "content": "    # TODO: retry"},
    {"path": "/workspace/util.py", "line": 8, "column": 1, "content": "# TODO: docstring"}
  ],
  "truncated": false,
  "files_scanned": 137
}
```

| Field               | Type    | Description                                                     |
| ------------------- | ------- | --------------------------------------------------------------- |
| `matches[].path`    | string  | Absolute path of the matching file                              |
| `matches[].line`    | integer | 1-based line number, or `0` for a name-only match               |
| `matches[].column`  | integer | 1-based column of the match start, or `0` for a name-only match |
| `matches[].content` | string  | The matching line, truncated at 4 KiB                           |
| `truncated`         | boolean | `true` when `max_results` was reached and more matches exist    |
| `files_scanned`     | integer | Number of files examined                                        |

## Behaviour notes

* **Literal by default.** Without `regex`, the pattern is matched as plain text, so `a.b`
  does not match `axb` and no escaping is required for user supplied input.
* **Binary files are skipped.** A file whose first 8 KiB contains a NUL byte, or which is
  not valid UTF-8, is not searched.
* **Large files are skipped.** Files above 8 MiB are excluded, keeping a search over a
  build tree bounded.
* **Directory symlinks are never followed.** The walk uses `lstat` semantics, so symlink
  cycles cannot hang the search and a link cannot be used to escape `path`.
* **Bounded work.** The walk stops at 200000 files regardless of `max_results`, and the
  compiled regular expression is size limited, so a pathological pattern cannot exhaust
  guest memory.
* **Glob matching applies to the name and the full path.** `*.py` matches `app.py` at any
  depth; `src/*.py` matches by relative path.
