AnthropicClaude Certified Architect – Foundations2026-102.5 Select and apply built-in tools (Read, Write, Edit, Bash, Grep, Glob) effectively
Follow exported names to indirect callers
Completed by the knowledge check
Learning objective
Trace code usage incrementally across imported modules, exported aliases, and wrappers rather than loading the entire codebase.
Core idea
A code generation agent needs a path through the code that explains the requested behavior. It does not need every file before it can ask the next useful question. Start from a known function, route, or error message, search for its location, read the relevant module, and follow the imports and exported names that connect that location to callers.
One textual search cannot establish every usage of an implementation. A module can re-export a function under another name, and a wrapper can call it behind its own public interface. You must discover those names and search for each one. The investigation grows from evidence, with each read producing the next search target rather than an invitation to load the whole repository.
How it works
Start from one behavioral question
Choose an entry point that connects directly to the request. If customers see “Refund exceeds captured amount,” use Grep.pattern to search that text. If the task names applyRefund, search that identifier. Use output_mode: "content" when you need a statement and location rather than only a filename. Search-tool scoping and availability are covered in 2.5.1.
Use Read.file_path on the relevant module, with offset and limit where a range is enough. Determine which definition emits the error, which conditions lead to it, and which imported functions participate in that path. Follow an import because it answers the next question about this behavior, not because every dependency deserves immediate expansion.
An import is a lead, not a complete explanation. A refund implementation might import both a capture lookup and a ledger writer. If the question concerns why refunds exceed available capture, the lookup is relevant; if it concerns missing ledger entries, the writer is relevant. Decide from the observed branch and the requested outcome instead of following an arbitrary directory order.
Follow names across module interfaces
Search for the implementation’s name to locate importers, calls, and re-exporting modules. Read a re-exporting index.ts rather than assuming it contains no behavior worth checking. Identify every exported name in that module that forwards or wraps the target function before searching for downstream users.
Consider these illustrative relationships in a billing package:
| Observed module relationship | Name to search next | Why it matters |
|---|---|---|
refunds.ts defines applyRefund | applyRefund | Finds direct mentions and modules exposing it |
index.ts exports applyRefund as processRefund | processRefund | Consumers can use the exported alias |
index.ts exports refundOrder, which calls applyRefund | refundOrder | Consumers call a distinct wrapper function |
A consumer imports processRefund as issueRefund | issueRefund within that consumer | The imported function has another local name |
A re-exported alias and a wrapper are different. The alias supplies another name for the target. The wrapper has its own body and can add validation, supply defaults, or transform arguments before calling the target. Read that body to determine whether its callers exercise the behavior you are tracing. Do not describe every wrapper as an interchangeable alias.
Search each discovered public name across the relevant codebase, then read the matching import and use together. If another module forwards a name again, repeat the same step for that module’s exports. A name is meaningful together with its module origin: two packages can contain an unrelated processRefund.
Keep the trace bounded and evidential
Treat search results as candidate edges in the trace. A comment mentioning refundOrder is not a call. A test importing it demonstrates a test dependency, not a production entry point. A file that imports an alias establishes a dependency, while reading the call establishes how the dependency is used.
At each step, retain the source path, the observed imported or exported name, and the statement connecting it to the next module. This lets you explain the path from caller to implementation without carrying every file into the final answer. If a branch remains unresolved, report the missing link instead of upgrading a plausible route into a verified one.
Anthropic’s best practices identify unscoped investigation as “the infinite exploration”: reading hundreds of files fills context. The context window includes conversation messages, file reads, and command output, and performance degrades as it fills. Bound the investigation by the behavior and verification criteria. A broad Glob followed by reading every source file defeats that boundary even if each individual read succeeds.
Stop when the discovered paths answer the requested question and the relevant verification can be selected. That is different from claiming a complete runtime call graph from text matching. If a discovered wrapper introduces another name or branch relevant to the requirement, continue; if an unrelated import does not affect the requested behavior, leave it outside the trace. Each read produces the next search target until the discovered paths answer the question.
Key distinctions and trade-offs
Implementation name versus consumer name: applyRefund can disappear from downstream source once a package exposes processRefund. Search the exported name after reading the re-export; repeating the original search cannot reveal a name it does not contain.
Alias versus wrapper: Both can conceal indirect usage from one search, but only a wrapper introduces a separate function body to inspect. Preserving wrapper argument handling can matter as much as changing the implementation.
Local understanding versus whole-codebase loading: A focused path uses less context and lets you inspect each relevant branch carefully. Its limitation is explicit scope: the findings must say which behavior and module paths were investigated. Smaller reads do not justify larger claims.
Search versus resolution: Grep searches text. A matching identifier can belong to a different module, and an imported alias can change the spelling at the call site. Reading the import and call together supplies the relationship that a bare identifier list lacks.
When active, the LSP tool uses a running language server to find symbol references, jump to definitions, and trace call hierarchies. It requires a code intelligence plugin for the language and an installed server binary; it stays inactive in cloud sessions because plugin language servers do not start there. Use it for symbol resolution; exported-name searches remain the exam guide’s assessed method.
Architecture / decision scenario
A Claude Code agent must add a required refund reason without breaking request handlers or scheduled jobs. It begins with Grep for the refund error text and reads refunds.ts, where applyRefund checks the captured amount and invokes a ledger writer.
Searching applyRefund finds a direct request handler and billing/index.ts. The agent reads that module and lists two exposed routes to the implementation: the re-export processRefund and the wrapper refundOrder. It searches both names before concluding that the direct request handler is the only affected caller.
The refundOrder search reveals a reconciliation job. Reading the wrapper shows that it constructs the argument object sent to applyRefund, so adding the required reason means examining that construction as well as the job. A processRefund importer renames it locally to issueRefund; reading that import tells the agent which local calls to inspect.
The agent follows the relevant ledger import to check where the reason is recorded, then selects the request-handler and job tests reached by those paths. It can explain implementation, public alias, wrapper, local alias, and downstream effect using a small set of observed files. It does not load every billing module to establish those connections or label unrelated identifier matches as affected callers.
Common exam traps
- Searching the original implementation name once and declaring every caller found.
- Skipping a re-exporting
index.tsbecause it looks like a directory manifest. - Searching only one of several exported names that expose the behavior.
- Treating a wrapper function as a pure alias without reading its argument handling.
- Ignoring a renamed import at the consumer’s call site.
- Reading every file before defining the behavior to trace.
- Turning a scoped textual investigation into a claim of complete runtime reachability.
Where the exam guide differs
The exam guide’s incremental trace assumes Grep. Claude Code omits it from the default tool set on macOS, Linux, and WSL, searching through Bash with grep instead. Restore it through --tools, through --allowedTools naming either Grep or Glob, or through a subagent tools list that includes Grep and omits Bash; see 2.5.1 for restoration scope. The method remains the same: find an entry point, read relevant code, follow imports, and search the exported names.
What to remember
Search an entry point, read its relevant code, follow imports, identify exported aliases and wrappers, and search each discovered name. Repeat only where the evidence opens another relevant path. Preserve the module relationships and unresolved gaps so the resulting explanation is both compact and defensible.
Knowledge check
A Grep for applyRefund returns a request handler and billing/index.ts. Refunds issued by a nightly reconciliation job also reach applyRefund, but the job’s file is not among the results. What should the agent do next?
billing/index.ts and Grep every name it exports for the functionGrep again with a looser pattern such as .*Refund.* to catch any similar nameGlob for files named **/*job* , then read each matching file in fullbilling/ so that no possible caller is missedbilling/index.ts exports applyRefund as processRefund and a function refundOrder that calls applyRefund. The agent is making a reason argument required, and searches have found callers of both names. Which next step is correct?
processRefund re-export to find the checks that it addsrefundOrder body to see how it calls applyRefundapplyRefund and skip both namesThe trace reaches jobs/reconcile.ts. A Grep for processRefund matches one line there: import { processRefund as issueRefund } from "../billing";. What should the agent conclude or do next?
issueRefund within that file and read the calls it findsGrep the repository again for processRefund with -i setGlob for the job’s test files and read those in place of the job itselfissueRefund , which a search for processRefund cannot find. Searching the local name inside that consumer and reading the calls establishes how the job uses the function. A case-insensitive search for the old name still misses the new spelling.An engineer asks why some refunds fail with “Refund exceeds captured amount” in a billing service of a few hundred files. The agent begins reading every file in src/billing/. Which approach should it take instead?
Glob for src/billing/**/*.ts , then read the files in the order returnedrefunds.ts imports, then decide which one mattersGrep the error text, read its branch, follow the import relevant to itReferences
- Anthropic, Tools reference
- Anthropic, Best practices for Claude Code
- Anthropic, Claude Certified Architect – Foundations Exam Guide