
November 3, 2026 · 6 min read
Grep: following exact matches through source code
Trace Pi's session writes with GitHits grep. Find callers, narrow matches and read the code that shows when listeners run relative to a file write.
An agent reading Pi’s session files needs to know whether a message completion event means the message is already in the file. Pi calls that event message_end.
In the search post, we found _persist, the method that writes session entries. Our grep tool can find occurrences of that name and help the agent follow the calls back to the event handler. It matches literal text or a regular expression in indexed source and documentation.
Find occurrences of a known name
The agent already has Pi’s repository address and a commit from the earlier investigation. We call the identifier passed to the tool a target. Here, github:earendil-works/pi@9c9ce854 selects that repository at commit 9c9ce854.
githits grep -F '_persist' github:earendil-works/pi@9c9ce854 --path-prefix packages/coding-agent/src/ -C 3
-F matches the supplied text literally. --path-prefix limits the files to the coding agent’s source directory. -C 3 includes three lines before and after each match.
This request returned four matches in packages/coding-agent/src/core/session-manager.ts:
| Line | Match |
|---|---|
| 1173 | The _persist method definition |
| 1198 | A call to this._persist(entry) |
| 1728 | A comment referring to _persist() |
| 1729 | Another comment referring to _persist() |
The comments and definition contain the same text as the call. The agent can narrow the pattern to the call’s spelling:
githits grep -F 'this._persist(' github:earendil-works/pi@9c9ce854 --path-prefix packages/coding-agent/src/ -C 3
That returned one match, at line 1198. Through MCP, the same grep request is:
{
"targets": [
{
"target": "github:earendil-works/pi@9c9ce854",
"path_selectors": [
{ "kind": "prefix", "value": "packages/coding-agent/src/" }
]
}
],
"pattern": "this._persist(",
"pattern_type": "literal",
"context_lines_before": 3,
"context_lines_after": 3
}
Follow the caller
The result’s file header gives the target and path to use with read. Reading the surrounding method shows that _appendEntry calls _persist:
githits read github:earendil-works/pi@9c9ce854 packages/coding-agent/src/core/session-manager.ts --lines 1192-1230
The same read includes appendMessage. It creates a message entry, passes it to _appendEntry and returns the entry’s ID. The agent can now look for callers of appendMessage:
githits grep -F 'appendMessage(' github:earendil-works/pi@9c9ce854 --path-prefix packages/coding-agent/src/ -C 2
This returned five matches across two files. One is the method definition. One is a comment. The other three are calls in agent-session.ts, including this location:
packages/coding-agent/src/core/agent-session.ts:1181
Read the event handler
Two lines of context around line 1181 show the call and a nearby branch. The event handling starts earlier, so the agent needs a larger range:
githits read github:earendil-works/pi@9c9ce854 packages/coding-agent/src/core/agent-session.ts --lines 1130-1205
The handler at this commit does the following in order:
- Awaits the extension event handler.
- Calls
_emitto notify public listeners. - Checks whether the event is
message_end. - Calls
appendMessagefor system, user, assistant and tool-result messages. Custom messages useappendCustomMessageEntry.
To check how listeners run, the agent can locate _emit and read its definition:
githits grep -F '_emit(' github:earendil-works/pi@9c9ce854 --path packages/coding-agent/src/core/agent-session.ts --limit 10 -C 2
githits read github:earendil-works/pi@9c9ce854 packages/coding-agent/src/core/agent-session.ts --lines 1054-1059
_emit calls each listener directly. A listener that reads the session file synchronously during message_end runs before this handler appends the message. An integration can’t use receipt of that event as confirmation that the message has been written.
This conclusion applies to the inspected commit and event path. The other appendMessage matches include writes for bash messages, which the agent would need to inspect separately.
Match several method names
The event handler revealed two append methods. A regular expression lets the agent find calls to either in one request:
githits grep 'this\.sessionManager\.append(Message|CustomMessageEntry)\(' github:earendil-works/pi@9c9ce854 --path packages/coding-agent/src/core/agent-session.ts -C 2
The expression matches this.sessionManager.append followed by either Message or CustomMessageEntry, then an opening parenthesis. It returned five matching lines in this file, including both calls in the event handler.
Regex is the default. For a copied error message, method name or configuration key, -F avoids treating punctuation as regex syntax. Matching is case-sensitive unless the agent supplies -i.
GitHits uses RE2 regex syntax. Lookaround and backreferences are unsupported. When searching across multiple files, the pattern must include fixed text that GitHits can use to narrow the search, such as the method-name text above. The grep documentation lists the supported controls.
Choose the files and check coverage
These controls let the agent adjust how much text it retrieves:
| CLI option | Effect |
|---|---|
--path | Select an exact file path |
--path-prefix | Select files under a path prefix |
--glob | Select paths with a glob pattern |
--corpus source | Limit repository files to source code |
--corpus documentation | Limit repository files to documentation |
-A, -B, -C | Include lines after, before or on both sides of a match |
--limit | Cap matches across the whole result page |
A package name and version can identify the repository source used to build a release. With a package target, file paths are relative to that package’s source directory. With a repository target, paths are relative to the repository root. Any public repository on GitHub, GitLab, Codeberg or Launchpad can be used without a published package.
grep also accepts hosted documentation sites. Package targets can include selected hosted documentation alongside repository files. The corpus and path filters apply to repository files, so they don’t exclude those hosted pages.
The response reports which sources it searched and whether any were omitted or still preparing. Match counts describe the current page. The _emit request above stopped at ten matches and returned a cursor for more. To continue, the agent adds the returned --cursor value to the same command, keeping the targets and matching controls unchanged.
An empty page can still have a continuation cursor or incomplete coverage. The agent should check those fields before concluding that a pattern is absent. A complete text search also depends on the spelling: calls through an alias won’t necessarily contain the method name it searched for.
With GitHits connected, try asking your agent:
Use GitHits to check whether Pi writes a message to its session file before notifying message_end listeners. Find the relevant calls with grep and read the handler and listener dispatch code. Keep the investigation on one commit.