search
Search file contents with a regex across files, directories, globs, and internal URLs.
Source
- Entry:
packages/coding-agent/src/tools/search.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/search.md - Key collaborators:
packages/coding-agent/src/tools/match-line-format.ts— model-facing anchor formatting.packages/coding-agent/src/tools/path-utils.ts— path normalization, glob splitting, internal URL resolution.packages/coding-agent/src/tools/file-recorder.ts— file ordering for grouped output.packages/coding-agent/src/tools/grouped-file-output.ts— grouped per-file text layout.packages/coding-agent/src/session/streaming-output.ts— line truncation and final byte truncation.packages/coding-agent/src/config/settings-schema.ts— default context lines.packages/natives/native/index.d.ts— nativegrep()types exposed to TS.crates/pi-natives/src/grep.rs— native regex/file search implementation.docs/natives-text-search-pipeline.md— native search pipeline overview.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
pattern |
string |
Yes | Regex pattern. search.ts trims it and rejects empty input. The native matcher enables multiline only when the pattern text contains a literal newline or the two-character sequence \\n. The model prompt explicitly documents literal-brace escaping such as interface\\{\\}, although the native layer also auto-escapes braces that cannot be valid repetition quantifiers. |
paths |
string | string[] |
Yes | One file path, directory path, glob-like path, archive member, internal URL, or an array of those. Append a line-range selector such as :50-100 or :5-16,960-973 to a single file/archive/internal-resource input to constrain matches. Empty strings and comma-joined multi-path entries are rejected after trimming/quote stripping. Filesystem-backed internal URLs search their backing file; virtual internal resources search resolved text in memory. Internal URLs cannot contain glob characters. |
i |
boolean |
No | Case-insensitive search. Defaults to false. Passed to native ignoreCase or JS RegExp flags for virtual resources. |
gitignore |
boolean |
No | Respect .gitignore during directory scans. Defaults to true. Passed to native gitignore. |
skip |
number |
No | File-page offset for multi-file results. Defaults to 0; search.ts floors finite numbers and rejects negative or non-finite values. Single-file searches ignore it because they do not paginate by file. |
Outputs
The tool returns a single text block in content[0].text plus structured details.
- Match lines are formatted by
formatMatchLine()as*LINE:contentfor matches andLINE:contentfor context under a¶PATH#TAGheader in hashline mode.- Hashline mode:
¶src/login.ts#1F2A,*5:content,9:content. - Plain mode:
*5|content,9|content.
- Hashline mode:
- Directory and multi-file results are grouped by file, with
# <path>#TAGheadings when editable hashline anchors are available and# <path>headings otherwise. detailsmay include:scopePath— formatted search scope.matchCount,fileCount,files,fileMatches— counts for the returned page.fileLimitReached— more matching files remain beyond the current 20-file page.perFileLimitReached— a hot file was trimmed to the per-file match cap.linesTruncated— one or more matched lines were shortened to512chars plus….truncatedandmeta.truncation— final text output was head-truncated bytruncateHead().displayContent— TUI-only rendering text with│gutters instead of model anchors.missingPaths— multi-path entries skipped because their base path did not exist.
- No-match result text is
No matches found, optionally followed by skipped missing-path or unreadable-archive notes.
Flow
SearchTool.execute()validates and normalizes input inpackages/coding-agent/src/tools/search.ts:- trims
pattern, rejects empty patterns; - normalizes
skipto a non-negative integer; - reads
search.contextBeforeandsearch.contextAfterfrom session settings (1and3by default); - enables multiline only when
patterncontains\nor an actual newline; - wraps a single string
pathsvalue into a one-element list and peels any line-range selector from each entry.
- trims
- Each
pathsentry is normalized withnormalizePathLikeInput(). - Archive member paths such as
bundle.zip:src/foo.tsare materialized to temporary UTF-8 scratch files before native grep. Binary or non-UTF-8 archive members are reported as skipped/unreadable. - Internal URLs are resolved before filesystem scope resolution:
- glob metacharacters (
*,?,[,{) are rejected for internal URLs; - resources with
sourcePathare searched through their backing file; - resources without
sourcePathare searched in memory with JavaScriptRegExp; omp://expands to every embedded documentation file via URL completion;- immutable sources are tracked so output can suppress editable hashline numbered output per file.
- glob metacharacters (
- For multi-path calls,
partitionExistingPaths()skips only ENOENT entries. If every filesystem entry is missing and no virtual internal resources remain, the tool errors. - Path resolution branches:
- one entry:
parseSearchPath()splitsbasePathand optional glob; - multiple entries:
resolveExplicitSearchPaths()computes a common base directory, brace-union glob, exact-file list, or degenerate-root target list.
- one entry:
- Line-range selectors are validated after path/archive/internal resolution. They are allowed only for single files, archive members, or virtual resources; glob/directory line-range selectors error.
search.tsstats the resolved base path to decide file vs directory behavior.- It calls native
grep()from@oh-my-pi/pi-nativeswith:pattern,ignoreCase,multiline,gitignore;hidden: true;cache: false;contextBefore/contextAfterfrom settings;maxColumns: DEFAULT_MAX_COLUMN(512);maxCount: INTERNAL_TOTAL_CAP(2000);mode: content.
- Native execution happens in
crates/pi-natives/src/grep.rs:
build_matcher()sanitizes non-quantifier braces before regex compile;- if compile fails with unopened/unclosed-group errors, it retries after escaping previously unescaped parentheses;
- directory scans use the grep pipeline described in
docs/natives-text-search-pipeline.md.
- Search dispatch differs by resolved path set:
- exact explicit files or degenerate-root multi-targets: JS loops over targets and merges
grep()results itself; - single file/directory base: one
grep()call handles native scanning.
- Virtual internal resources are searched in JS with
RegExp; archive scratch paths and virtual paths are remapped back to user-facing selectors before rendering. - JS output shaping then:
- caps multi-file output to 20 files per page (
DEFAULT_FILE_LIMIT), usingskipas the next file offset; - caps matches per file to 20 for multi-file scopes and 200 for single-file scopes;
- round-robins selected per-file matches so one file does not monopolize the page;
- formats lines through
formatMatchLine()for the model andformatCodeFrameLine()for TUI; - records non-truncated matched/context lines into the session file-read cache with
recordSparse().
- Final text is passed through
truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER }), so the effective cap is the default byte cap fromstreaming-output.ts, not the default line cap. toolResult()attaches text plus limit/truncation metadata.
Modes / Variants
- Single file path
grep()searches one file.- Output is a flat list of match/context lines.
- Visible limit is the first
200matches after native matching and JS per-file capping.
- Single directory path or single glob-like path
parseSearchPath()may split the input intopath+glob.- One native
grep()scans the directory tree withgitignoreandhidden:true. - Results are grouped into a 20-file page; use
skipwith the next file offset shown in the limit message. - JS round-robins the selected files' matches.
- Multiple explicit paths/globs
resolveExplicitSearchPaths()collapses them into a common base and either a brace-union glob, an explicit file list, or per-target searches when the only common base is the filesystem root.- Missing entries are skipped non-fatally unless all are missing.
- Archive member paths
- Supported for UTF-8 text entries only. The member is extracted to a temporary scratch file for native grep, then displayed as
archive.ext:member.
- Supported for UTF-8 text entries only. The member is extracted to a temporary scratch file for native grep, then displayed as
- Internal URL paths
- Filesystem-backed resources search their resolved
sourcePath. - Virtual resources without
sourcePathsearch their resolved content in memory. omp://expands to all embedded documentation files so it can be used as a docs search root.- No internal-URL globbing.
- Immutable and virtual sources suppress editable hashline anchors.
- Filesystem-backed resources search their resolved
Side Effects
- Filesystem
- Stats resolved search roots and input paths.
- Reads matched files through native
grep(). - Records sparse matched/context lines into the session file-read cache via
getFileReadCache(...).recordSparse(...).
- Session state (transcript, memory, jobs, checkpoints, registries)
- Reads session settings for context defaults.
- Uses
session.internalRouterto resolve internal URLs. - Populates tool
details.metawith truncation/limit metadata.
- Background work / cancellation
- Wrapped in
untilAborted(signal, ...)at the JS level. search.tsdoes not passsignalortimeoutMsinto nativegrep(), so native grep cancellation/timeouts are not used by this tool.
- Wrapped in
Limits & Caps
- File page limit:
20files (DEFAULT_FILE_LIMITinpackages/coding-agent/src/tools/search.ts). - Per-file match caps:
20for multi-file scopes (MULTI_FILE_PER_FILE_MATCHES),200for single-file scopes (SINGLE_FILE_MATCHES). - Native/JS preselection cap:
2000matches (INTERNAL_TOTAL_CAP). - Line truncation:
512characters per emitted line (DEFAULT_MAX_COLUMNinpackages/coding-agent/src/session/streaming-output.ts). Native grep marks truncated lines; JS reportslinesTruncated. - Final text truncation:
truncateHead()default byte cap50 * 1024bytes (DEFAULT_MAX_BYTESinpackages/coding-agent/src/session/streaming-output.ts).search.tsoverridesmaxLinestoNumber.MAX_SAFE_INTEGER, so normal search output is byte-capped, not line-capped. - Context defaults:
search.contextBefore = 1,search.contextAfter = 3inpackages/coding-agent/src/config/settings-schema.ts. - Pagination:
skipis a file-page offset for multi-file scopes. The result text saysUse skip=<N> for the next pagewhen more files remain. - Native directory-scan cache: available in
grep.rs, but this tool always setscache: false.
Errors
Pattern must not be emptywhen trimmedpatternis empty.Skip must be a non-negative numberfor negative or non-finiteskip.`paths` must contain non-empty paths or globswhen any normalized path is empty.paths is an array — pass ["a", "b"] not ["a,b"] ...for comma-joined multi-path entries outside brace expansion.Glob patterns are not supported for internal URLs: ...for internal URL + glob metacharacters.- Line-range selector errors include
Line-range selector requires a single file, not a glob: ...,Line-range selector requires a single file: ... is a directory, andPath not found for line-range selector: .... Cannot search archive member(s): ...when all archive selectors are unreadable, binary, or non-UTF-8.Path not found: ...when a filesystem-backed resolved base path is missing, or when every multi-path filesystem entry is missing.- Virtual internal URL regex compile failures are reported as
Invalid regex: ...from JavaScriptRegExp; filesystem-backed regex failures beginning withregexorregex parse errorare normalized toInvalid regex: .... - Multi-file native scans skip per-file open/search failures inside
grep.rs; the scan continues with surviving files.
Notes
- The model-facing prompt documents Rust regex syntax for filesystem-backed searches and JavaScript
RegExpfor virtual internal URL content. - Native
build_matcher()already auto-escapes braces that cannot be valid quantifiers, so patterns like${platform}become searchable instead of failing. Valid quantifiers likea{2,4}remain unchanged. - Native compile retry also escapes unescaped literal parentheses only after an unopened/unclosed-group parse error. It is a fallback, not a general parser mode.
- Internal URLs are resolved before path existence checks. Backed resources become ordinary filesystem paths; virtual resources stay in memory and do not mint editable hashline anchors.
hidden:trueis hard-coded insearch.ts; there is no model-facing flag to exclude dotfiles.gitignore:falseonly affects native directory traversal. It does not disable the tool's own path normalization or explicit-file handling.- When
pathsresolves to multiple exact files, each target uses the2000internal cap before JS grouping. - The section tag in hashline mode is a four-hex opaque snapshot tag from the session snapshot store;
searchrecords whole-file snapshots when possible and prints bare line numbers beneath the header.