Why does my AI coding agent keep writing deprecated code, and how do I stop it?

The short version: the model in your agent favors patterns common in its training data, and from that alone it has no live view of the library you use today. Functions get renamed and deprecated, so it writes deprecated code. The agent around the model can do better: prevent the drift by changing what it reads before it generates, detect what slips through, and correct it by feeding results back.

TL;DR

  • Models favor high-probability training patterns. A coding model favors high-probability continuations from its training data, and from that training alone it does not track any library’s current version.
  • “Common in training” and “current” diverge. In fast-moving ecosystems, maintainers rename and deprecate APIs, so “high-probability in the training data” and “current and correct” pull apart over time.
  • Deprecated patterns win by default. When an older, now-deprecated pattern was common in training, it tends to be what the model reaches for unless something in the prompt or tooling overrides it.
  • The agent can be fed current context. The agent around the model can read repository context, manifests, retrieval results, and docs tools, so the fix is to feed it current, specific material rather than let the model rely on memory.
  • The work is prevention, detection, and correction. The training-data prior is the cause this article focuses on, but stale retrieval, stale repo examples, and version confusion cause deprecated code too, so the practical work is prevention, detection, and correction.

On this page

What is actually happening

Here is the situation that sends most people to a search box. You name the version. You paste the current docs. The agent still reaches for the function that was renamed two releases ago, and it does it confidently, in idiomatic-looking code. So the first thing to get straight is where the behavior comes from, because the obvious guess (the model is “out of date” and needs a patch) is only half the story.

Start with the mechanism. A language model predicts the next token from patterns it saw during training. Its training distribution is the statistical picture of code it learned from, and its prior is the pull that picture exerts before you give it any specific instruction. If the version of a library the model saw most often used a now-deprecated function, that function is what it tends to reach for by default. Not because it is current, but because it was common in the data. The model is not consulting today’s changelog. It is favoring a probable pattern from a snapshot of the past.

One refinement, because it matters later. The model does not always emit the single most probable continuation. Depending on how it is decoded, it samples among the high-probability ones. What stays consistent across decoding settings is the pull toward what was common before the cutoff. So think of the prior as a bias in the distribution, not a fixed lookup.

Why does that bias produce wrong code rather than merely old code? Because libraries move. In fast-moving ecosystems their maintainers deprecate, rename, and restructure APIs, and the registries that distribute them (npm, PyPI) simply carry each new version. A function that was idiomatic two years ago may be renamed in a later major version, moved, or removed. The longer it has been since the cutoff, the more releases the model never saw. Not every release breaks compatibility, so the gap does not widen on a fixed schedule, but exposure to drift grows with time.

It is worth naming the failure types, because “deprecated code” is really four different things and the fixes differ. A deprecated API still exists and usually works, but is discouraged. A removed API is gone and fails outright. A wrong-version API is valid, just in a different version than the one you run. All three come from the same training-staleness family this article covers. A hallucinated API, one that never existed, has a different cause and is out of scope here. Keeping these apart is the difference between “my build failed” and “my build passed and shipped a warning nobody read.”

Now, how strong is that pull? This is where I have to be careful, because it is the easy place to overclaim. When an older, now-deprecated pattern was common in training, it tends to be what the model produces unless something overrides it. That is a tendency, not a rate and not a law. Frequency in the corpus is only one factor, since data quality, duplication, post-training, and the decoding settings all intervene. So nobody can read the strength of the pull off a raw count, and I am not going to pretend a number here that the evidence does not support.

The useful part is that the same account tells you where the fixes have to live. Prompts and context change what the model reads at generation time. They do not change the trained model itself. So when you put current material in front of it, the version-matched docs, the installed version’s types, an explicit version constraint, you change what it conditions on, which makes the current API more likely to be chosen than the remembered one. That is a mechanism, not a measured success rate, and it is not guaranteed. The old pattern can persist despite grounding, and weak grounding (a vague version mention, a stale doc snippet, a partial signature) can lose to the prior.

Two clarifications keep this honest, because both are places the story could get too neat. First, the agent around the model can read repository context, your manifest, retrieval results, docs tools, and sometimes the web, all of which supply post-cutoff information. That is why the fixes below work at all: they use channels the agent already has, so frozen memory is the default, not the ceiling. Second, the training-data prior is not the only cause of deprecated code. Retrieval can return a stale doc, the repo’s own examples can be out of date, or versions can conflict. Those are different, related causes this article does not cover, and it is worth diagnosing which one you have before reaching for a fix.

This is a studied problem, not folklore, so it is worth separating what developers report from what researchers have measured. Developers discuss it directly, for example in this r/webdev thread, this r/programming thread, and this r/FlutterDev thread. Read those as practitioner reports of a felt problem, not as a rate. On the research side, work on updating code models describes how a model’s static knowledge does not reflect the fact that libraries and API functions continuously evolve, so code that was correct at training time drifts out of date (CodeUpdateArena, Liu et al., 2024). Related work studies version-conditioned generation, targeting the version you actually have rather than the one the model remembers, as its own hard problem (GitChameleon, Islah et al., 2024).

What you can do today

You cannot turn off the training prior. What you can do is reduce deprecated output, detect what gets through, and correct it. It helps to sort the moves into three kinds up front, because they act at different points: prevention changes what the model reads before it generates, detection catches bad output after the fact, and correction connects the two. None of the moves below require a tool from us.

Prevention (change what the model reads):

1. Ground the model in the version your code actually runs against. That is the version that matters, not an incidental local install. This distinction trips people up, so a concrete trace: a package.json or pyproject.toml usually holds a version constraint, a lockfile records a resolved version, and pip show or npm ls report whatever is installed in the environment you query. Those three can disagree. So identify the version in the environment that runs the code (it may be the lockfile-resolved or the deployment-image version), state it in the prompt, and pass the manifest and lockfile as supporting context. This removes one common source of version confusion. It does not resolve conflicting transitive dependencies or an agent that ignores what you provide.

2. Give the model the docs for the version you actually run, not simply the latest. This is the point most people miss, and it is the one I got wrong myself before thinking it through: the newest documentation can describe APIs that arrived after your version, which reintroduces wrong-version output. So “give it the docs” is not enough. Paste the version-matched documentation, or wire up a retrieval tool that returns it. Retrieval-augmented generation (RAG) and an MCP server (Model Context Protocol, a standard way to connect tools to an agent) are common ways to put that material in the context window, the working memory the model reads for this request. Retrieval helps only if it returns docs that match your installed version. A tool that fetches the latest page, or a stale one, grounds it in the wrong thing.

3. Paste the actual signatures or types from your installed version. This is the narrowest, highest-signal grounding: it conditions the generation toward the shape you actually run rather than the shape the model remembers. It is not airtight. The model can still ignore or misread them, and separately maintained type stubs can diverge from runtime, so the typechecker (below) is what actually catches a mismatch.

One caveat across all three: feed context only from trusted sources and approved tools, and avoid pasting secrets. Build output, repository context, and third-party retrieval results can carry credentials or prompt-injection content, so grounding material is not automatically safe just because it is useful.

Detection (catch it after generation):

4. Run typecheck, lint, and build, then read what they report. In typed setups these catch many removed or type-changed APIs, since those fail to compile. Coverage depends on configuration, and this is where the failure types from earlier come back: dynamic languages may only fail at runtime, types may be absent or permissive, and a deprecated-but-still-working call usually passes, because deprecation is a warning, not a removal. So enable and read deprecation warnings specifically, not just hard errors. The signal you most need is the one that does not stop the build.

5. Review the diff before you accept it. Deprecated calls are dangerous precisely because they look plausible. They read as idiomatic and a build will not always flag them. Reading the diff is where the version drift that typecheck misses gets caught, which is to say it is a check on the model’s confidence, not just its syntax.

Correction (close the loop):

6. Feed the errors and warnings back. Hand the typecheck failures, lint output, and deprecation warnings back to the agent and ask it to fix them. This is the move that turns a one-shot guess into a correction loop: the toolchain’s report becomes new material the next attempt conditions on. It is the same conditioning mechanism as prevention, just fed by current signals the model did not have the first time.

That is the whole shape. Prevention changes what the model reads before it generates. Detection catches bad output after the fact and does nothing to the model on its own. Correction connects them, feeding the problem back so the next generation is re-conditioned. If you only adopt one, adopt correction, because it recovers the value of detection you were otherwise throwing away.

If you maintain a library, you sit on the other side of this and can make your current API easier for agents to reach for: keep docs current and versioned, mark deprecations clearly in code and docs (for instance, the in-code deprecation annotations your ecosystem supports), provide migration notes between major versions, and expose current type surfaces. What agents actually consume varies, so prefer whatever mechanisms your ecosystem’s tools already read rather than betting on one channel.

A short diagnostic. When you see deprecated code, it pays to figure out which cause you are looking at before you reach for a fix, because the wrong fix wastes a round trip. These checks point at a cause rather than prove one:

  • No current context given? It is likely working from training memory. Start with prevention.
  • Using a docs or retrieval tool? Check what it actually returned. Stale retrieval grounds the model in old material even when a tool is enabled, which looks identical to no grounding from the outside.
  • Matches your repo’s own examples? If those are out of date, the agent may just be copying them, and no amount of docs will help until you fix the examples.
  • Declared a version that is not the one installed? Reconcile the prompt with the installed metadata.

What still is not measured

Now the honest edge of this, because it is easy to blur. The benchmarks above do measure something real: CodeUpdateArena on controlled API-update tasks, GitChameleon on version-conditioned generation. They answer “can a model handle this update task.” That is a measurement of model behavior, not a measurement of impact in your codebase.

The open question is a different one, and I want to state it precisely so it is clearly not what the papers answer: how often, across a category’s common tasks, an agent’s first attempt uses an API that is deprecated or wrong for the version in use. Nobody has measured that yet, at least not publicly, that we are aware of. The two benchmarks come closest, and they still measure the model on a task, not the default behavior in the wild.

This sits next to a broader blind spot: what an agent reached for by default is hard to see reliably at a category level. That is a related question, not the same one, and we cover it separately in why your product analytics cannot see agent selection.

Measuring that real-world default behavior is the kind of thing we do at NativeFold. The reason this section exists is that it is measurable, and measuring it is our work rather than a slogan. To see the method, read our methodology.

Frequently asked questions

Why does my AI assistant suggest outdated code? It favors high-probability patterns from training data that has a knowledge cutoff, so an older pattern tends to surface unless something overrides it. Watch for a second cause with the same symptom: stale retrieval or out-of-date examples in your own repo produce the same result for a different reason.

How do I stop my AI assistant from using deprecated functions? You reduce, detect, and correct rather than fully stop, because the prior itself does not switch off. Before it writes, ground it in the version you run, the version-matched docs, and the actual signatures you have. Then run typecheck, lint, and build, read the deprecation warnings, review the diff, and feed anything you catch back for another attempt.

Do AI coding agents know the current version of a library? It varies by product and configuration. The base model on its own does not: it has a static snapshot from before its cutoff. But depending on the setup, an agent can read the version from repository context, your manifests, your installed metadata, or a docs tool. Version-conditioned generation is a known hard problem (GitChameleon).

Is this a bug I should report? Sometimes, and it is worth separating the two cases. The model’s knowledge cutoff is structural, not a bug a single tool can patch. But repeated deprecated output can reflect a fixable product problem: weak retrieval, poor version detection, ignored context, or no deprecation-warning feedback. When an agent ignores a version you gave it, skips a docs tool you enabled, or mishandles a deprecation warning, report it.

Does retrieval-augmented generation fix deprecated code? It helps, because it can put version-matched documentation in the context window so the model conditions on current material instead of relying on memory. The honest caveat: it only helps if it returns material that matches your version, and the old pattern can persist despite grounding. RAG changes what the model conditions on. It does not guarantee a current answer.

References

Practitioner reports

Individual developer discussions about AI coding assistants, cited at the level of their titles. They are examples that the topic is discussed, not a measured sample, and no rate should be read from them.