coding agents
The MCP C# SDK's Copilot instructions tell agents to use a class Copilot deleted
Contents
Every coding agent opens the same file first. Before it writes anything it reads the repository’s instruction file, whichever of CLAUDE.md, AGENTS.md or .github/copilot-instructions.md the project keeps, and takes what that file says about the code as a starting point. Here is line 258 of the one in the official C# SDK for the Model Context Protocol, the protocol whose whole job is to give models accurate context:
Server Implementation
- 256Server primitives (tools, prompts, resources) are discovered via reflection using attributes
- 257Support both attribute-based registration (
WithTools<T>()) and instance-based (WithTools(target)) - 258Use McpServerFactory to create server instances with configured options
Not in the code.Deleted 2 Dec 2025 in #985.
The line, at the commit I checked. This file exists to brief a coding agent before it writes anything.
McpServerFactory does not exist. Search the repository for the name and you get one result: this line, in the file every agent reads first. The class was marked obsolete in September 2025 with a note to use McpServer.Create instead, and deleted in December.
McpServerFactory in the whole repository: this lineTo be precise about the cost: an agent that takes line 258 at face value writes McpServerFactory, watches the build fail, and goes looking for what it should have done. A more careful agent greps for the name first, finds nothing, and does the same search a few turns earlier. Either way the damage is a few wasted turns, which is cheap. The reason I wrote this up is what it says about every other sentence in that file, and in yours.
How the line got there and stayed
Nobody involved would have caught this, because nothing they did required them to:
One line, five commits
- 16 Sep 2025
McpServerFactoryis marked[Obsolete]: "Use McpServer.Create instead. This member will be removed in a subsequent release." 38b4a26 - 13 Oct 2025Copilot opens #858, "Set up Copilot instructions for repository", a long and mostly accurate briefing. A maintainer reviews and merges it. The instruction to use
McpServerFactoryis line 222. - 2 Dec 2025#985, "Remove obsolete APIs from codebase", deletes
McpServerFactory.cs. Authored by Copilot, co-authored by three of the project's developers. The instructions file is not in the diff, so nobody reviewing the removal opened it. - Apr to Aug 2026Four more commits edit the instructions file. None touches the line, which drifts down to 258.
- TodayLine 258 is still there.
The reviewer of the instructions read a long document that was almost entirely right. The reviewer of the removal read a diff, and the document was not in it. That is the whole mechanism. A document and the code it describes live in different files, and a pull request only ever shows you one of them.
The agent repeats the wrong sentence
Stale names do not just sit there. In April 2026 a commit authored as “Architecture Bot” added an Architecture section to the README of yegor256/cactoos. The commit message links a Claude Code session. The new section says:
Explicit caching requires opting in with
StickyorStickyList.
StickyList was renamed away in December 2018, and the list class it became was removed in 2020. The old name survived in a comparison table further up the same README. An agent asked to describe the architecture read that table, and the name came out the other side as a confident new instruction with a fresh timestamp. The next agent to open that README finds two sentences recommending a class that has been gone for seven years, and the newer one looks authoritative.
Why your pipeline does not catch this
Design docs used to have one reader, a person, who noticed when a sentence had stopped being true. Now the document is an input to code generation. The practice has a name, spec-driven development, and a toolchain: GitHub’s Spec Kit, AWS’s Kiro, Tessl, and the AGENTS.md and CLAUDE.md files that brief an agent before it touches anything. Birgitta Böckeler’s survey of those three tools sorts the practice into three levels: spec-first, where the spec drives one task; spec-anchored, where it is kept afterwards and the feature keeps evolving through it; and spec-as-source, where a human edits only the spec and never the code. Everything past the first level depends on the spec staying true after the task ends, and that is the step the tooling has not caught up with.
Everything else your code is built from has a check: source has a compiler, tests have a runner, types have a checker, manifests have a resolver. Prose has some checks too, and they are worth naming so nobody thinks I am pretending otherwise. Rustdoc warns on a broken intra-doc link. Doctests run the examples. A link checker catches a dead URL. None of them reads a sentence. “Use McpServerFactory” is not a link, not an example and not a URL, so it passes all of them.
The obvious answer is to grep. git grep -w McpServerFactory -- '*.md' takes a second, and if every removal PR ran it, this post would have nothing to show you. I would genuinely like everyone to do that, and it is the first item in the list at the end. It also only catches the version of the problem you can see. The expensive version has no name in it.
Two ways an agent strays from the docs
It follows a sentence that is no longer true
"Use McpServerFactory." The build breaks, the agent recovers, you pay in turns and tokens. If the sentence describes a pattern rather than a class, the build does not break, and the agent reproduces a design the team retired.
It ignores a sentence that is still true
"Controllers go through the service layer." "core never imports from plugins." The agent takes a shortcut. The code compiles, the tests pass, the diff looks fine, and the document that forbade it is not in the diff. Merged.
The second case is where architecture goes: a hundred small changes, each of which compiled, each reviewed as a diff, each contradicting a sentence nobody had open. A year later the layering the team agreed on describes a system that no longer exists. Grep cannot find that, because there is no token to search for. Checking “core never imports from plugins” means turning the sentence into a question about the dependency graph and asking it at both ends of the pull request.
It is not rare
The MCP line is the one I chose to lead with because the irony is hard to beat, but it was not hard to find. In the same sweep of public repositories, DolphinScheduler’s contributor guide sends new contributors to implement an interface the repository no longer has, Apache Pinot’s design note says its SQL DDL module depends only on three things while the module’s build and imports pull in a fourth, and BenchmarkDotNet documents four properties of an interface that no longer exists.
What to do about it
- Treat your agent instruction files as code. They are an input to your codebase now. When you delete or rename a type,
git grep -w OldName -- '*.md'takes a second, and IDE rename refactorings skip markdown. - Keep them short, and prefer rules to names. “Controllers never call repositories directly” stays true across a hundred refactors. A class name is a claim that can go stale on the next one.
- Have the docs checked on the pull request, where the change that contradicts them is being reviewed, by something that reads the sentence and the code together.
That last one is what I build, which is how I found these. Striff is a GitHub App, free on public repositories, that parses both revisions of a pull request, reads the documents already in the repository, turns each sentence that makes a claim about the code into a rule, and checks it at the base and the head. A name the repository does not have, like line 258, is reported against the page with the commit that removed the type. A rule the change broke is reported against the change. There is nothing to write and nothing to configure, because the rules are the ones your team already wrote down and your agents are already reading. The sweep these examples came from, with its numbers and a worked example from sentence to verdict, is in a post on the Striff blog.
I opened fixes for both lines before publishing this: modelcontextprotocol/csharp-sdk#1892 (with issue #1893) and yegor256/cactoos#1959.