File Operations
These tools cover reading, listing, searching, and locating files. They are read-only — no file is modified. For writing and editing, see Editing.
All paths are relative to the active project root unless you supply an absolute path. Reads are subject to the project’s security deny-list (e.g. SSH keys and credential files are blocked by default).
See also: Output Buffers — how large file reads are stored as
@file_idrefs rather than dumped into context.
read_file
Purpose: Read the contents of a file, optionally restricted to a line range.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | File path relative to project root |
start_line | integer | no | — | First line to return (1-indexed) |
end_line | integer | no | — | Last line to return (1-indexed, inclusive) |
Example — read an entire file:
{
"path": "src/main.rs"
}
Output:
{
"content": "fn main() {\n println!(\"Hello\");\n}\n",
"total_lines": 3
}
Example — read a specific range:
{
"path": "src/main.rs",
"start_line": 10,
"end_line": 25
}
When you supply both start_line and end_line, the tool returns exactly those lines with no overflow cap applied. When neither is supplied and the file exceeds 200 lines, only the first 200 lines are returned and an overflow field tells you how to retrieve the rest:
{
"content": "... first 200 lines ...",
"total_lines": 850,
"overflow": {
"shown": 200,
"total": 850,
"hint": "File has 850 lines. Use start_line/end_line to read specific ranges"
}
}
Tips:
- Use
symbolsorsymbolsto locate the line range of a function before callingread_file— this lets you fetch exactly what you need without reading the whole file. - For large files, prefer reading in chunks with explicit
start_line/end_lineover reading the whole file. - If you want to search for a pattern rather than read, use
grepinstead.
Source-range gate
When start_line / end_line is supplied on a source file (.rs, .ts, .py, and other languages codescout recognises), the tool checks whether the requested range overlaps a named symbol body. If it does, the request is blocked and the error names the overlapping symbol:
Error: range 45–72 overlaps symbol `MyStruct/validate` — use symbols(name='validate', include_body=true) instead
This steers you toward symbols(include_body=true), which is robust to line-number shifts caused by later edits.
Bypass: Add "force": true to skip the gate and return the raw content regardless:
{ "path": "src/model.rs", "start_line": 45, "end_line": 72, "force": true }
Scope: Only recognised source files are gated. Config files, Markdown, TOML, JSON, and other non-code formats are never affected.
tree
Purpose: List files and directories under a path. Pass recursive=true for a full tree.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | Directory path relative to project root |
recursive | boolean | no | false | Descend into subdirectories |
detail_level | string | no | compact | "full" to show all entries without the exploring-mode cap |
offset | integer | no | 0 | Skip this many entries (focused-mode pagination) |
limit | integer | no | 50 | Max entries per page in focused mode |
Example — shallow listing:
{
"path": "src"
}
Output:
{
"entries": [
"/home/user/project/src/main.rs",
"/home/user/project/src/lib.rs",
"/home/user/project/src/tools/"
]
}
Directories are suffixed with /. In exploring mode the output is capped at 200 entries; if the directory has more, an overflow field appears with guidance.
Example — full recursive tree:
{
"path": "src",
"recursive": true
}
Hidden files and paths matched by .gitignore are excluded automatically.
Tips:
- Start with a shallow listing to understand the top-level structure, then drill into subdirectories of interest.
- Use
recursive=trueonly when you need the full tree. On large repositories a recursive walk can produce many entries; narrow it with a more specificpathif you hit the overflow cap. - To find files by name pattern use
tree(with glob) instead — it supports glob patterns and is faster for targeted searches.
grep
Purpose: Search the codebase for a regex pattern. Returns matching lines with file path and line number.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
pattern | string | yes | — | Regular expression to search for |
path | string | no | project root | Directory to restrict the search to |
max_results | integer | no | 50 | Maximum number of matching lines to return |
Example:
{
"pattern": "fn\\s+validate_\\w+",
"path": "src",
"max_results": 20
}
Output:
{
"matches": [
{
"file": "/home/user/project/src/util/path_security.rs",
"line": 14,
"content": "pub fn validate_read_path("
},
{
"file": "/home/user/project/src/util/path_security.rs",
"line": 38,
"content": "pub fn validate_write_path("
}
],
"total": 2
}
The search walks the directory tree using the same .gitignore-aware walker as tree. Binary files that cannot be decoded as UTF-8 are silently skipped. The regex engine enforces size limits to prevent pathological patterns from hanging.
Tips:
- Use
pathto narrow the search when you already know which part of the codebase is relevant — this is significantly faster on large repos. - Increase
max_resultsif you expect many matches and need to see them all. - When you know a symbol name,
symbolsis more precise than a regex search because it uses the LSP index. Usegrepwhen you are looking for text patterns, string literals, comments, or constructs that the LSP does not model as symbols. - To find files by name (not content), use
tree(with glob).
tree (with glob)
Purpose: Find files matching a glob pattern. Respects .gitignore.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
pattern | string | yes | — | Glob pattern (e.g. **/*.rs, src/**/mod.rs) |
path | string | no | project root | Directory to search within |
max_results | integer | no | 100 | Maximum number of file paths to return |
Example:
{
"pattern": "**/*.toml",
"path": "."
}
Output:
{
"files": [
"/home/user/project/Cargo.toml",
"/home/user/project/.codescout/project.toml"
],
"total": 2
}
Example — find all test files in a subdirectory:
{
"pattern": "**/test_*.py",
"path": "tests"
}
The glob is matched against the path relative to the search directory, so **/*.rs will match files at any depth. The walker respects .gitignore, so build artifacts, vendored dependencies, and other ignored paths are excluded.
Tips:
- Prefer
tree(with glob) overtreewhen you are looking for files by name — the glob match is more expressive than scanning a directory tree manually. - Use
grepwhen you need to find files by their contents rather than their names. - The
**wildcard matches across directory boundaries. Use it for language-wide searches like**/*.rsor to locate files with a specific name anywhere in the tree:**/Makefile.
create_file
Purpose: Create a new file or overwrite an existing file with given content.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | File path relative to project root |
content | string | yes | — | Full file content to write |
Example:
{
"tool": "create_file",
"arguments": {
"path": "src/utils/helpers.rs",
"content": "pub fn clamp(v: f64, min: f64, max: f64) -> f64 {\n v.max(min).min(max)\n}\n"
}
}
Output: "ok"
Tips:
- Creates parent directories if they don’t exist.
- Overwrites without warning — check that the path is correct before writing.
- For editing existing files, use
edit_fileinstead.
See Editing for more usage guidance.
edit_file
Purpose: Find-and-replace editing within an existing file. Matches an exact string and replaces it — whitespace-sensitive.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | File path relative to project root |
old_string | string | yes | — | Exact text to find (must match including whitespace) |
new_string | string | yes | — | Replacement text |
replace_all | boolean | no | false | Replace every occurrence instead of just the first |
insert | string | no | — | "prepend" or "append" — add text at the start/end of the file |
Example — change an import:
{
"tool": "edit_file",
"arguments": {
"path": "src/main.rs",
"old_string": "use crate::utils::old_helper;",
"new_string": "use crate::utils::new_helper;"
}
}
Output: "ok"
Tips:
old_stringmust match exactly — including indentation and line endings.- Use for imports, constants, config values, and small literal changes.
- For changes to a function or struct body, prefer
edit_code(action="replace")— it’s robust to line number shifts.
See Editing for full parameter details and more examples.