2.5 · Lesson 5 of 5
Built-in Tools
What You Need to Know
Claude Code provides six built-in tools for working with codebases: Read, Write, Edit, Bash, Grep, and Glob. Each has a specific purpose, and using the wrong tool for a task wastes time, context tokens, or both. The exam deliberately presents scenarios where confusing these tools leads to incorrect answers.
Grep vs Glob: The Core Distinction
This is the distinction that matters most in this task statement. Get it wrong and you'll lose marks.
Grep searches file contents for patterns. Use Grep when you need to find text inside files: function callers, error messages, import statements, and variable assignments. Any time you are searching for what files contain, Grep is the tool.
// Find all files that call processLegacyOrder() Grep: "processLegacyOrder" // Find all error messages containing "timeout" Grep: "timeout" // Find all files that import a specific module Grep: "import.*from 'utils/auth'"
Glob matches file paths by naming patterns. Use Glob when you need to find files by name, extension, or directory structure: filenames, test files, configuration files, and all files of a given type in a directory. Any time you are searching for files based on their path, Glob is the tool.
// Find all test files Glob: "**/*.test.tsx" // Find all configuration files Glob: "**/config.*" // Find all MDX files in the domains directory Glob: "content/domains/**/*.mdx"
The distinction in one sentence: Grep finds what is INSIDE files. Glob finds files by their NAMES.
The exam presents scenarios where a developer uses the wrong tool. Glob cannot search file contents for function callers — it matches paths, not contents. Grep is not the purpose-built tool for matching file paths. It can technically find the word "test" inside content, but the exam expects Glob for naming patterns such as **/*.test.tsx.
Read, Write, and Edit
These three tools handle file operations, each optimised for a different use case.
Edit performs targeted modifications using unique text matching. You specify the exact text to find (old_string) and its replacement (new_string). It is fast and precise because it touches only the specific text you identify.
Edit: old_string: "function processOrder(id: string)" new_string: "function processOrder(id: string, validate: boolean = true)"
Edit requires a unique match. If the text you specify appears in multiple places in the file, Edit can't tell which occurrence you mean, so it fails. That is a safety mechanism, not a bug — it stops you changing text you never meant to touch.
When Edit can't find a unique anchor: the exam's answer. The exam guide names one fallback, Read + Write. Read the full file, then Write the complete modified version back. It works every time, because you are no longer asking Edit to guess. It also spends a file's worth of tokens on what was usually a one-line change, which is why it is the fallback and not the default.
When Edit can't find a unique anchor: current Claude Code. The Edit tool docs now give you a cheaper move first: widen old_string with more surrounding context until it pins down one location, or set replace_all: true if you actually want every occurrence updated. Both keep you on Edit and cost almost no extra context. In real work, do that before you reach for Read + Write.
The ordering in real work:
- Try Edit with the shortest anchor that is plausibly unique.
- On a non-unique match, widen
old_stringuntil it matches one location, or usereplace_all: trueif you want every occurrence changed. - Fall back to Read + Write only when neither of those can disambiguate the target.
The ordering on the exam has two steps: Edit first, Read + Write when Edit fails. Both orderings agree on the first step. Don't default to Read + Write for every modification. The exam penalises that because it burns context tokens.
Incremental Codebase Understanding
How you explore a codebase matters as much as which tools you use. Reading the entire codebase upfront is a context-budget killer. A 200-file codebase read in full swallows the context window, mostly on files that have nothing to do with the task. No other exploration mistake costs more.
The right approach is incremental discovery. Start narrow. Expand only as needed.
- Grep to find entry points. Search for the function name, class name, or error message that anchors the investigation. This tells you which files are relevant.
- Read to follow imports and trace flows. Once you know which files matter, Read them to understand the code structure. Follow import statements to discover related files.
- Grep again to trace usage. The files you read may expose the function under another name: a wrapper (
submitOrder()that callsprocessOrder()inside it) or a barrel file that re-exports it (export { processOrder as submitOrder }). Callers of the new name never mention the original, so the first Grep never saw them. Grep for each new name, across the whole codebase, to get the full list of consumers. - Read only what you need. Each file you read should be justified by what you discovered in the previous step.
That is minimal context for maximum understanding. You map the codebase progressively, spending tokens only on files that matter to the task.
Tracing Function Usage Across Wrapper Modules
A common codebase pattern: a function is defined in one module, re-exported through a wrapper, and consumed through the wrapper's name. A simple Grep for the original name misses every consumer who imports through the wrapper.
The correct approach:
- Grep for the function definition to find where it is defined.
- Read the defining file to identify exported and renamed names.
- Grep for each exported name across the codebase to find all consumers.
- If a barrel file re-exports the function (for example
index.ts), trace that barrel's module name and Grep for consumers who import from it.
Concretely: processOrder is defined in orders.ts. The barrel utils/index.ts re-exports it as submitOrder, and three of its five consumers import submitOrder from utils. A Grep for processOrder finds the definition, the barrel line, and the two consumers that import the original name. It cannot find the other three, because the string processOrder never appears in their files. Read the barrel, spot the rename, Grep for submitOrder, and the three turn up.
orders.ts → utils/index.ts → exported as submitOrder → consumers import submitOrder. A single Grep for processOrder misses those consumers. The multi-step trace catches indirect consumers a single Grep would miss.
The Deprecation Scenario
Find every file that calls a deprecated function and the test files that exercise it. The correct sequence is Grep, then Glob, then Grep again — not Glob first.
- Grep for the function name — finds every file whose contents reference
processLegacyOrder, including any tests that import it directly (content search). - Glob for sibling test files — finds the test file that pairs with each caller by naming convention, for example
OrderProcessor.ts→OrderProcessor.test.tsxandRefundHandler.ts→RefundHandler.test.tsx, even when the test exercises the function indirectly through the source module (path matching). - Grep again for wrapper names — when a caller exposes the function through a wrapper (for example
applyLegacyOrdercallsprocessLegacyOrderinternally), Grep for the wrapper name to find tests that cover the function transitively through it.
Say Grep reveals that OrderProcessor.ts and RefundHandler.ts call the deprecated function. Glob for **/OrderProcessor.test.* and **/RefundHandler.test.* to pull in OrderProcessor.test.tsx and RefundHandler.test.tsx, even if those tests never mention processLegacyOrder by name. If either source file wraps the function under a new name, Grep for the wrapper to catch any remaining tests.
This is Grep → Glob → Grep: content search for direct references, path matching for adjacent tests, content search for indirect coverage.
Exam traps
Using Glob to find function callers (it searches paths, not contents)
Glob matches file paths by naming pattern. It cannot search inside files for function calls. Use Grep to search file contents for function names, import statements, or error messages.
Using Grep to find files by extension or naming pattern
While Grep could technically find filenames mentioned in content, Glob is the purpose-built tool for matching file paths. Use Glob for **/*.test.tsx, **/config.*, and similar path-based searches.
Reading all source files upfront before understanding what is relevant
Loading every file into context is a context-budget killer. The correct approach is incremental: Grep to find entry points, then Read to trace flows from those specific entry points.
Defaulting to Read + Write for every file modification instead of trying Edit first
Edit is faster and uses less context because it only touches the specific text. Read + Write loads the entire file. Try Edit first. Read + Write is the fallback for when Edit cannot find a unique anchor, not the standard response.
Answering 'widen old_string or set replace_all' when a question asks what to do after Edit reports a non-unique match
That is what current Claude Code does, and it is the cheaper move in real work. The exam guide names Read + Write as the fallback when Edit cannot find unique anchor text, and every keyed answer follows the guide. Read the full file, then Write the complete modified version.
Practice scenario
A developer needs to find all files that call a deprecated function processLegacyOrder() and also find all test files for those callers. Which tool sequence is correct?
Build exercise
Trace and Refactor a Deprecated Function Using Built-in Tools
30 minutes
What you'll learn
- Apply Grep for content search and Glob for path matching in the correct sequence
- Use incremental codebase discovery instead of reading all files upfront
- Select Edit as the primary modification tool
- Understand widening old_string / replace_all in current Claude Code
- Trace function usage across wrapper modules and barrel files
- Follow the Grep-then-Glob pattern for callers and tests
Step 1
Use Grep to search for all callers of a target function such as processLegacyOrder
Search file contents for the deprecated function name so every direct call site turns up with its file and line.
Why: Grep searches file contents — it is the correct tool for finding function callers. Using Glob here would fail because Glob matches file paths, not contents. The exam tests this distinction directly and penalises candidates who confuse the two.
You should see: A list of file paths containing calls to processLegacyOrder, with line numbers and matching lines showing the exact call sites. For example: src/OrderProcessor.ts:42: await processLegacyOrder(orderId).
Step 2
Use Glob to find test files matching the caller filenames
After Grep names the callers, match their sibling tests by path, for example **/*.test.tsx or **/OrderProcessor.test.*.
Why: Glob matches file paths by naming pattern — it is the correct tool for finding test files by extension or naming convention. This completes the Grep-then-Glob pattern: content search to find callers, then path matching to find their tests.
You should see: A list of test file paths matching the pattern, such as src/OrderProcessor.test.tsx and src/RefundHandler.test.tsx. These correspond to the caller files found by Grep in the previous step.
Step 3
Use Read to examine each caller file and understand the usage pattern
Read only the files Grep identified, and note parameters, return values, imports, and any wrapper that renames the function.
Why: Reading files incrementally — only after Grep identifies which files matter — is the correct approach. Reading all source files upfront is a context-budget killer that the exam explicitly penalises. Each Read should be justified by what you discovered in the previous step.
You should see: The full contents of each caller file, showing how processLegacyOrder is called, what parameters are passed, how the return value is used, and whether the function is imported directly or through a wrapper module.
Step 4
Use Edit to replace the deprecated function call with the new API
Replace processLegacyOrder(orderId) with processOrder(orderId, { validate: true }) using a unique old_string and new_string.
Why: Edit is the preferred modification tool because it targets specific text and uses less context than Read + Write. The exam penalises defaulting to Read + Write for every modification. Always try Edit first — it is faster and more precise.
You should see: Each caller file updated with the new API call replacing the deprecated one. For example, processLegacyOrder(orderId) replaced with processOrder(orderId, { validate: true }). The Edit tool confirms the replacement was made successfully.
Step 5
When Edit reports a non-unique match, widen old_string or use replace_all, and fall back to Read + Write only if the target still cannot be disambiguated
In current Claude Code, add surrounding context until old_string matches one location, or set replace_all: true when every occurrence should change. Read + Write is the last resort, and it is the exam guide's keyed fallback.
Why: Widening the anchor or replace_all keeps the operation on Edit and uses less context than loading the whole file. Edit fails when the target text appears multiple times — that is a safety mechanism. Per the Edit tool documentation, expand the anchor until it matches one place, or use replace_all for a global replacement. Read + Write loads the entire file for what is usually a single-line change, so keep it as a last resort. That is the practice answer. On the exam, the guide names Read + Write as the fallback when Edit cannot find unique anchor text, so answer that.
You should see: The first Edit attempt fails because old_string matches multiple locations (for example: old_string matches 3 locations). The second attempt, with a wider unique anchor that includes surrounding context, succeeds and changes exactly one occurrence. If replace_all: true was the right call, every occurrence is updated.