Select and apply built-in tools (Read, Write, Edit, Bash, Grep, Glob) effectively
The built-in file and shell tools (Read, Write, Edit, Bash, Grep, Glob) are available in every Claude Code and Claude Agent SDK session before any MCP server is added, and choosing the right one per step is a genuine tool-selection skill. Searching file contents is Grep's job, matching filenames is Glob's, surgical edits are Edit's, and whole-file rewrites are Write's, with Bash reserved for running commands. Picking the wrong tool wastes context and turns, and the exam probes the specific edge cases: Grep versus Glob, Edit's unique-anchor requirement, the Read+Write fallback, and incremental exploration instead of reading everything upfront.
The six built-in tools at a glance
Claude Code and the Claude Agent SDK ship a small, fixed set of file and shell tools that every session can use before any MCP server is added: Read, Write, Edit, Bash, Grep, and Glob. Choosing the right one for each step is a tool-selection skill in its own right, and picking the wrong one wastes context, tokens, and turns.
Think of them in three jobs. Discovery: Glob finds files by path or name pattern, Grep searches inside files for content. Reading: Read loads a file's full contents with line numbers. Writing: Write replaces a whole file, Edit makes a targeted in-place change. Bash runs shell commands (build, test, git) and is not a substitute for the search or read tools.
The exam tests that you reach for the tool whose job matches the step: search contents with Grep, match filenames with Glob, change one spot with Edit, rewrite a file with Write, and run commands with Bash. Distractor answers typically swap two of these, for example using Glob to find a function definition, or shelling out to grep in Bash to search the codebase.
Grep searches contents; Glob matches paths
Grep is a content search: it looks inside files for a pattern. Use it to find every caller of a function, locate a specific error message string, or find all files that import a module. It is powered by ripgrep, so it takes a regular expression and supports filtering by file glob or type, case-insensitive matching, and context lines. Output modes include files_with_matches (just the paths, the default), content (matching lines, optionally with -n line numbers and -A / -B / -C context), and count.
Glob is a path search: it matches file names and paths against a glob pattern and returns the matching paths, sorted by modification time. Use it when you know a naming convention but not the location, for example **/*.test.tsx to find every React test file, or src/**/*.ts to enumerate a package's TypeScript sources.
The one-line rule: if you are looking for text that lives inside files, use Grep; if you are looking for files by their name or extension, use Glob. Confusing the two is a classic distractor. Glob will never find a function body, and Grep is the wrong tool for 'list all the test files.'
Grep pattern='def process_refund' output_mode='content' -n=true # who defines / calls it
Glob pattern='**/*.test.tsx' # every test file
Read, Write, and Edit, and the unique-anchor rule
Read loads a full file and returns it with line numbers; you can page large files with an offset and a limit. Write creates a file or overwrites an existing one wholesale. Edit performs an exact string replacement: you give it old_string (the text to find) and new_string (the replacement).
Two hard requirements make Edit reliable. First, you must Read a file before you Edit or Write an existing one; the tools refuse to modify a file the session has not seen. Second, old_string must match exactly one location in the file. If the anchor text appears more than once, Edit fails rather than guessing which occurrence you meant. You resolve this by adding enough surrounding lines to old_string to make it unique, or by setting replace_all when you truly intend to change every occurrence.
Edit old_string='const timeout = 30' new_string='const timeout = 60'
# error: 'timeout = 30' appears 3 times -> not a unique match
Use Edit for surgical changes and Write when you are replacing most of a file or generating it fresh. Write is heavier: it rewrites the entire file, so it costs more tokens and can clobber other changes if used carelessly.
Read + Write as the fallback when Edit can't find a unique anchor
When Edit reports that its old_string is not unique and you cannot easily disambiguate it, the reliable fallback is to Read the whole file, construct the corrected contents, and Write the file back. This sidesteps the uniqueness constraint entirely because Write does not depend on matching an anchor.
This is the specific pattern the blueprint calls out. It is safer than the tempting alternative of replace_all, which changes every occurrence of the anchor, often not what you want when only one of several identical lines should change. Read+Write lets you place the change exactly where it belongs by rewriting the surrounding context deliberately.
The trade-off is cost and blast radius: Read+Write moves the entire file through context and rewrites all of it, so prefer a more specific Edit (with extra surrounding lines to make the anchor unique) when that is feasible, and reserve full Read+Write for cases where the anchor genuinely cannot be made unique or the change is extensive.
Explore incrementally: Grep for entry points, Read to follow the flow
When you land in an unfamiliar codebase, do not Read every file to 'understand it first.' That floods the context window with mostly irrelevant content, triggers lost-in-the-middle omissions, and degrades answer quality over a long session. Build understanding incrementally instead.
Start with Grep to locate entry points: the route handler, the CLI command, the exported function named in the task. From a hit, Read just that file, note what it imports, then Grep or Read the next hop in the chain. You follow imports and call sites outward, pulling in only the files that are actually on the path you care about.
This mirrors the domain-5 context-management guidance: keep the working set small and relevant. A good exploration transcript is a tight alternating rhythm of narrow Grep searches and targeted Reads, not a wall of full-file dumps at the start.
Tracing usage across wrapper and barrel modules
A function is often re-exported through wrapper or 'barrel' modules, for example an index.ts that does export { doThing } from './impl', sometimes renaming it. A single Grep for the original name will miss call sites that import it under a different name or through the barrel.
The reliable technique is two-phase: first identify all the exported names the symbol travels under (grep the export and re-export statements to see how it is surfaced), then Grep for each of those names across the codebase to find every caller. This catches aliased and re-exported usages that a single-name search overlooks.
Grep pattern='export .* processRefund' # find how it is exported / re-exported / aliased
Grep pattern='processRefund|refundOrder' # then search every name it is used under
This is the built-in-tool version of 'find all callers': the accuracy of the trace depends on enumerating the names first, not on one lucky search string.
Bash: run commands, not file search or reads
Bash executes shell commands in a persistent working directory: running the test suite, building, git operations, invoking scripts, moving files. It is the tool for doing things to the system, and for many tasks it is essential.
What it should not be is a stand-in for the dedicated tools. Reaching for grep -R, find, cat, head, tail, or sed in Bash is discouraged when Grep, Glob, and Read exist. The dedicated tools are faster (Grep is ripgrep under the hood), return results in a structure the model handles well (line numbers, path lists, context), respect ignore rules, and integrate with the permission system. Shelling out for search also produces raw, unpaginated output that bloats context.
So the split is clean: use Grep / Glob / Read to inspect the codebase, and Bash to run the commands that act on it. A distractor that 'searches the codebase' via bash grep -r is choosing the wrong tool even though it would technically work.
Anti-patterns to avoid
Why it fails: Bash search produces raw, unpaginated output that floods context, bypasses the permission and ignore-rule integration, and is slower; Grep (ripgrep) and Glob return structured results the model uses more reliably.
instead Use Grep for content, Glob for filenames, and Read for file contents; reserve Bash for running commands (tests, build, git).
Why it fails: It fills the context window with mostly irrelevant content, triggers lost-in-the-middle omissions, and degrades reliability over the session.
instead Grep for entry points, Read only those files, and follow imports outward, expanding the working set only as the task requires.
Why it fails: replace_all changes every occurrence of the anchor, so identical lines you did not intend to touch get modified too, silently introducing bugs.
instead Add surrounding lines to make the anchor unique, or fall back to Read the full file and Write back the corrected contents so only the intended spot changes.
Why it fails: Glob matches paths and names and cannot see inside files; Grep searches contents and is the wrong fit for enumerating files by naming convention.
instead Match the tool to the target: Grep for anything inside files (function names, error strings, imports), Glob for files by name or extension pattern.
Worked example: Tracing a refund flow through an unfamiliar service (Scenario 4)
Task. In the Scenario 4 developer-productivity agent, an engineer asks: 'Change processRefund to retry 5 times instead of 3, and make sure every caller still works.' You have never seen this service.
Step 1, find the definition, don't read everything. Resist Read-ing the whole src/ tree. Grep for the definition:
Grep pattern='function processRefund|const processRefund' output_mode='content' -n=true
# -> src/payments/refund.ts:42
Step 2, Read just that file and follow imports. Read src/payments/refund.ts. You see the retry constant and notice the file is re-exported by a barrel module.
Step 3, enumerate the names it travels under. The barrel aliases it:
Grep pattern='processRefund' path='src/index.ts' output_mode='content'
# -> export { processRefund as refundOrder } from './payments/refund'
So callers may use processRefund or refundOrder.
Step 4, find every caller across both names.
Grep pattern='processRefund|refundOrder' output_mode='files_with_matches'
Now you have the true caller list, including the aliased usages a single-name search would have missed.
Step 5, make the change, and handle a non-unique anchor. You try a targeted Edit:
Edit old_string='retries = 3' new_string='retries = 5'
# error: 'retries = 3' matches 2 locations -> not unique
The constant appears twice (the refund path and, separately, a reconciliation call). You do NOT want replace_all, because only the refund path should change. Two clean options: add surrounding lines to make the anchor unique, or fall back to Read + Write:
Read src/payments/refund.ts # load full contents
Write src/payments/refund.ts # write back with ONLY the refund retry set to 5
Step 6, verify with Bash. Searching and editing used Grep / Read / Edit / Write; running the suite is Bash's job:
Bash: npm test -- payments
The whole trace stays small and relevant: narrow Grep searches and targeted Reads instead of dumping the codebase into context, plus a Read+Write fallback exactly when Edit could not find a unique anchor.
Exam tips
- ✓Grep searches file CONTENTS (regex, powered by ripgrep); Glob matches file PATHS and NAMES (e.g., **/*.test.tsx). Distractors swap them.
- ✓Edit needs a UNIQUE old_string and requires the file to have been Read first; a non-unique anchor makes Edit fail rather than guess.
- ✓When Edit's anchor is not unique, the reliable fallback is Read the full file then Write it back, not replace_all (which changes every occurrence).
- ✓Explore incrementally: Grep for entry points, then Read to follow imports and trace the flow. Reading all files upfront wastes context and causes degradation.
- ✓To find all callers across wrapper or barrel modules, first enumerate every exported name (including aliases), then Grep each name across the codebase.
- ✓Use Bash for running commands (tests, build, git), not for searching or reading; prefer Grep / Glob / Read over bash grep, find, and cat.
Official exam objectives for 2.5
- Grep for content search (searching file contents for patterns like function names, error messages, or import statements)
- Glob for file path pattern matching (finding files by name or extension patterns)
- Read/Write for full file operations; Edit for targeted modifications using unique text matching
- When Edit fails due to non-unique text matches, using Read + Write as a fallback for reliable file modifications
- Selecting Grep for searching code content across a codebase (e.g., finding all callers of a function, locating error messages)
- Selecting Glob for finding files matching naming patterns (e.g., **/*.test.tsx)
- Using Read to load full file contents followed by Write when Edit cannot find unique anchor text
- Building codebase understanding incrementally: starting with Grep to find entry points, then using Read to follow imports and trace flows, rather than reading all files upfront
- Tracing function usage across wrapper modules by first identifying all exported names, then searching for each name across the codebase
Flashcards from this lesson
Grep vs Glob: which searches file contents and which matches file paths?
Grep searches inside files for content (patterns, function names, error strings, imports). Glob matches file names and paths by pattern (e.g., **/*.test.tsx). Contents = Grep, paths = Glob.
What must be true of Edit's old_string, and what happens if it isn't?
It must match exactly one location, and the file must have been Read first. If the anchor is non-unique, Edit fails rather than guessing. Fix by adding surrounding context to make it unique, using replace_all to change all occurrences, or falling back to Read + Write.
Edit can't find a unique anchor for a one-line change. Most reliable fallback?
Read the whole file, build the corrected contents, and Write it back. This avoids the uniqueness constraint. Prefer it over replace_all when only one of several identical lines should change.
How do you correctly explore an unfamiliar codebase with built-in tools?
Incrementally: Grep for entry points, Read that file, follow its imports with more Grep and Read, tracing the flow outward. Don't Read every file upfront, since that wastes context and degrades reliability.
Which tool finds every file matching **/*.test.tsx, and which finds every line that throws a specific error string?
Glob finds the files by path pattern; Grep finds the error string inside file contents.
Why prefer Grep / Glob / Read over bash grep, find, and cat?
The dedicated tools are faster (Grep is ripgrep), return structured, paginated results the model handles well, respect ignore rules and permissions, and avoid flooding context. Bash is for running commands (build, test, git), not searching or reading.
How do you trace all callers of a function re-exported through barrel or wrapper modules?
Two phases: first Grep the export statements to enumerate every name (including aliases) the symbol is exposed under, then Grep for each of those names across the codebase.