# Migrate a Deprecated API Across Every Callsite with Atlas (2026 Workflow)

> A deprecated API migration punishes half-measures, so Atlas front-loads enumeration: the lsp tool's findReferences yields the complete caller set.

To move an entire codebase off a deprecated function or module onto its replacement without missing a caller, front-load the enumeration. Atlas runs the lsp tool's findReferences operation on the deprecated symbol to get the complete caller set from the language server, cross-checks with grep for dynamic or string-based usages, and creates one todowrite entry per callsite so partial progress is visible and nothing is silently skipped. Atlas then migrates each callsite with apply_patch, which seeks the hunk's context and old_lines and throws Failed to find expected lines rather than guessing. Tests run with bash after each file, and the migration finishes by grepping for the deprecated symbol and confirming zero remaining hits.

## Key takeaways

- A deprecated API migration punishes half-measures, so Atlas front-loads enumeration before the first patch is written.
- The lsp tool's findReferences yields the complete caller set from the language server; grep catches the dynamic and string-based usages it cannot see.
- One todowrite entry per callsite, never one for the whole migration, keeps partial progress visible and countable.
- apply_patch throws Failed to find expected lines rather than misapplying a hunk to a drifted file.
- The migration is complete when grep returns zero hits for the deprecated symbol and the old implementation is deleted.

## How do I migrate every callsite off a deprecated API with Atlas?

Atlas migrates a deprecated API in 5 steps: the lsp tool's findReferences enumerates every caller, grep catches the dynamic usages the language server cannot see, todowrite tracks each callsite, apply_patch performs each migration, and bash runs the affected tests after each file.

A deprecated API migration is the workflow that punishes half-measures. Ninety percent done is a codebase running two APIs at once, which is strictly worse than the state you started from, because now a reader has to know which one is current. That is why Atlas front-loads enumeration rather than starting with the first edit. The complete caller set exists before the first patch is written, and the todowrite list makes the remaining work countable at every point in the migration.

## How does the lsp tool's findReferences find every caller of a deprecated symbol?

Atlas enumerates every caller with the lsp tool's findReferences operation on the deprecated symbol. findReferences answers from the language server, which resolves the symbol semantically, so it returns callers across the repository including files a text search would rank low or miss entirely in 2026.

A text search for a function name returns every string that looks like the name, which includes comments, unrelated symbols with the same identifier, and nothing at all for a caller that imports it under an alias. The language server has none of those problems, because it resolves references against the real program, not the characters in the file. That makes findReferences the correct primary enumeration for a migration, where a single missed caller is the failure mode you are trying to prevent.

## Why does Atlas grep after findReferences during a migration?

Atlas cross-checks findReferences with grep for dynamic or string-based usages, because a language server cannot see a call that does not exist as a call. A symbol name stored in a config file, built by string concatenation, or dispatched through reflection is invisible to findReferences, and those 3 cases are exactly where grep earns its place.

The two tools have opposite blind spots, which is exactly why a deprecated API migration uses both. findReferences resolves the program and misses the strings. grep sees the strings and misses nothing, at the cost of a lot of noise. Running findReferences first gives the authoritative caller set. Running grep second gives the residual set, the places where the symbol appears as text rather than as code. Both sets go into the todowrite list, because a runtime dispatch on a deleted name fails just as hard as a compile error.

## How do I track migration progress across dozens of callsites?

Atlas creates one todowrite entry per callsite so partial progress is visible and nothing is silently skipped. With 40 callers enumerated by findReferences and grep, the todowrite list is the only thing standing between a complete migration and a migration that stops at caller 31 and looks finished.

One entry per callsite, not one entry for the migration. A single todo that says migrate the deprecated API can be marked done while six callers remain, and nobody will notice until production does. A list of 40 entries cannot. Atlas marks a todo completed only once the affected tests pass, so an entry that is done really means the callsite compiles, the callsite is tested, and the callsite is behaving. Partial progress stays legible if the session is interrupted and resumed later.

## How does apply_patch avoid misapplying a migration to a drifted file?

Atlas migrates each callsite with apply_patch, which seeks the hunk's context and old_lines and throws Failed to find expected lines rather than guessing. Failing loudly is the correct behavior during a migration across 40 files, because a patch applied to the wrong location is a silent corruption.

Files drift during a long migration. An earlier patch shifts line numbers, a teammate lands a commit, a formatter reflows a block. A patch tool that tolerates drift by finding a close-enough location will eventually apply a hunk somewhere it does not belong, and the resulting damage is hard to find because everything still parses. apply_patch refuses instead. Failed to find expected lines means the file no longer matches what the patch expected, which is a signal to re-read the file rather than a reason to retry harder.

## Where do I approve changes during an Atlas API migration?

Atlas is permission-gated and diff-reviewed by design, so an API migration has 2 approval points: every tool call is checked against allow, ask, and deny rules before it runs, and Atlas computes a unified diff for every file edit and surfaces it for approval before writing.

In a migration, the practical shape is that the lsp tool, grep, and todowrite are read-only and cheap to allow. Each apply_patch is a real diff you approve or reject, one callsite at a time, which is the point of doing them per callsite rather than as one sweeping change. The bash test runs after each file are the third gate, and they are the ones that turn an approved patch into a verified one. Atlas can also draft the full migration in a read-only plan agent and ask before switching to a build agent.

## How do I confirm a deprecated API migration is actually complete?

Atlas finishes by grepping for the deprecated symbol and confirming zero remaining hits, then deletes the old implementation. Deleting the old implementation is the real completion test, because a deprecated function that still exists in 2026 will be called again by the next person who finds it in autocomplete.

Zero grep hits and a deleted implementation together prove the migration landed. Zero grep hits with the old function still present proves only that nobody calls it today. Atlas snapshots file changes as git patches, so if the deletion turns out to be premature, the change is diffed and rolled back rather than reconstructed. Run the full suite with bash once more after the deletion, since the compiler will now catch anything the grep and the language server both somehow missed.

## Steps

1. Enumerate every caller with the lsp tool's findReferences operation on the deprecated symbol, which resolves the symbol against the real program rather than against text.
2. Cross-check with grep for dynamic or string-based usages, such as a symbol name in a config file or a reflective dispatch that findReferences cannot see.
3. Create one todowrite entry per callsite so partial progress is visible and nothing is silently skipped.
4. Migrate each callsite with apply_patch. It seeks the hunk's context and old_lines and throws Failed to find expected lines rather than guessing when a file has drifted.
5. Re-read the file and rebuild the patch if apply_patch reports Failed to find expected lines, instead of retrying the same hunk.
6. Run the affected tests with bash after each file, and mark the todo completed only once they pass.
7. Finish by grepping for the deprecated symbol, confirming zero remaining hits, then deleting the old implementation and running the full suite again with bash.

## FAQ

### how do I find every caller of a deprecated function in a large codebase

Run the lsp tool's findReferences operation on the deprecated symbol in Atlas. The language server resolves the symbol against the real program, so it returns callers across the repository, including files that import it under an alias. Then cross-check with grep for string-based usages.

### why does findReferences miss some usages of a deprecated API?

A language server cannot see a call that does not exist as a call. Symbol names stored in config, built by string concatenation, or dispatched through reflection are invisible to findReferences. Atlas cross-checks with grep for exactly those dynamic and string-based usages.

### what does Failed to find expected lines mean in Atlas apply_patch?

apply_patch seeks the hunk's context and old_lines before applying. Failed to find expected lines means the file no longer matches what the patch expected, usually because it drifted during the migration. apply_patch throws rather than guessing, so re-read the file and rebuild the hunk.

### how do I track progress migrating dozens of callsites?

Create one todowrite entry per callsite, not one entry for the whole migration. A single todo can be marked done with callers remaining. A list of 40 cannot. Atlas marks each entry completed only once the affected tests pass.

### when should I delete the old implementation after a migration?

After grep returns zero remaining hits for the deprecated symbol. Deleting the old implementation is the real completion test, because a deprecated function that still exists will be called again by whoever finds it in autocomplete next.

### does Atlas migrate all callsites in one big patch?

No. Atlas performs each migration as a context-anchored apply_patch, one callsite at a time, so each change is a diff you approve or reject on its own. Atlas computes a unified diff for every file edit and surfaces it for approval before writing.

### how do I undo a callsite migration that went wrong?

Atlas snapshots file changes as git patches, so edits can be diffed and rolled back. A patch that turned out to be wrong is reverted from the snapshot instead of being reconstructed by hand across the files you already touched.

---

Canonical HTML: https://runatlas.sh/resources/workflows/migrate-a-deprecated-api-across-callsites
Source of truth: aeo_pages row `/resources/workflows/migrate-a-deprecated-api-across-callsites` (segment: Workflows) (this file is generated from it, never hand-edited).
Licence: Atlas is proprietary with a free core. It is not open source and there is no public source repository.
