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 (CLAUDE.md, AGENTS.md or .github/copilot-instructions.md, depending on the agent) and takes what that file says about the code as a starting point.
I build Striff, a tool that checks docs against code on pull requests. I was running it over public repositories when it flagged line 258 of the instruction file in the official C# SDK for the Model Context Protocol, the protocol whose whole job is to give models accurate context:
Server Implementation Architecture
- 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 Copilot reads first. The class was marked obsolete in September 2025 with a note to use McpServer.Create instead, and deleted in December.
The cost of this one line is small. 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 it is a few wasted turns. I wrote this up because of what it says about every other sentence in that file, and in yours.
How the line got there and stayed
At no point in the history of this line was anyone looking at the instruction and the class at the same time.
One line, five commits
- 16 Sep 2025
McpServerFactoryis marked[Obsolete]: "Use McpServer.Create instead." 38b4a26. A week later the note gains a second sentence: "This member will be removed in a subsequent release." 5f99299. - 13 Oct 2025Copilot opens #858, "Set up Copilot instructions for repository", a long and mostly accurate briefing. Two maintainers review it and it is merged. The instruction to use
McpServerFactory, by then obsolete for four weeks, is 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 had a reason to open it. - Apr to Aug 2026Four more commits edit the instructions file. None touches the line, which drifts down to 258.
- 26 Sep 2026Line 258 is still there.
The people who reviewed the instructions read a long document that was almost entirely right. The people who reviewed the removal read a diff, and the document was not in it. A document and the code it describes live in different files, and a pull request shows only the files it changes.
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 Spec Kit, Kiro and Tessl 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.
In a survey of 335 open-source repositories that document their architecture, 63% already give their coding agents documents to build from, and 8% of those run any tool that checks the code against them. The survey is mine, and the link goes to how it was counted.
Code has a compiler and a test runner to tell you when it is wrong. Prose has a few checks too: rustdoc warns on a broken intra-doc link, doctests run the examples, and a link checker catches a dead URL. But “Use McpServerFactory” is a plain sentence, and none of those tools reads sentences, 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 little to show you. I would like everyone to do that, and it is the first item in the list at the end. It only catches the kind of drift that has a name in it, though, and the expensive kind does not.
Two ways this goes wrong
The agent 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.
The agent 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 how architecture erodes: a hundred small changes that each compiled and each passed review as a diff, while 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.
Other repositories
I led with the MCP line, but lines like it were not hard to find. In the same run, DolphinScheduler’s contributor guide tells 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.
These are well-maintained projects with careful reviewers, and their review process is fine. It just never puts the sentence and the code in front of the same person.
A stale name can also get copied. The README of yegor256/cactoos kept StickyList in a comparison table years after the class was renamed and then removed. In April 2026 an agent asked to describe the architecture wrote a new section that told readers to opt in to caching “with Sticky or StickyList”.
What to do about it
- Treat your agent instruction files as code, because agents build from them. When you delete or rename a type,
git grep -w OldName -- '*.md'takes a second, and IDE rename refactorings usually 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.
Striff, the tool that found these
Striff is a GitHub App, free on public repositories. It 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 the rule at the base and the head. A name the repository no longer has, like McpServerFactory, is reported against the document that uses it, with the commit that removed the type. A rule the change broke is reported against the change. You do not write any rules yourself, because it uses the ones your team already wrote down and your agents are already reading.
Here is a real check, unedited, from a public pull request. The run these examples came from covered 609 public pull requests. Striff read 7,161 rules out of those repositories’ docs, could answer 5,674 of them from the parsed code, and found 97 that the change under review had broken. The full breakdown is on the Striff blog.
The fixes
I opened fixes for the MCP line and the cactoos sentence before publishing this. yegor256/cactoos#1959 was merged the next day. modelcontextprotocol/csharp-sdk#1892 (with issue #1893) is still open.