A Plan Analysis Skill That Refuses to Let You Cite Cost
I have been testing the rules in my own T-SQL style guide against real instances, which means reading a lot of execution plans. Partway through, I started using Erik Darling’s sqlserver-query-plans plugin[1] for the reading.
It changed one of the posts, and not by finding something I had missed. It stopped me reaching a conclusion I was already comfortable with.

What it is
It is a skill for an AI coding agent: a set of instructions plus an extraction script that the agent loads when it encounters a query plan. It is distributed as a plugin.
The mechanical part is a Python extractor that reads a .sqlplan file and prints a digest. That alone solves a real problem. Plans are large, they are usually UTF-16 so text searches against them match nothing and report no error, and a plan that has been opened and re-saved often contains UTF-8 bytes while still declaring encoding="utf-16". The plans I generated for one post ran to two megabytes each.
The more interesting part is the analytical instructions wrapped around it, which are largely a list of conclusions you are not allowed to reach.
The rule that matters most
The skill is emphatic that cost is never evidence:
Cost is always an estimate. Always. The “Query cost (relative to batch): 97%” figure that SSMS puts at the top of a plan is computed from the optimizer’s guesses. It is present in actual plans. It is not a measurement.
Its digest prints Estimated subtree cost: 0.58 (ALWAYS an estimate), with the parenthetical attached every time, which is harder to ignore than a number in a tooltip.
This matters because cost percentage is the most visually prominent number in a plan and the easiest to quote. I have written cost comparisons myself, in One NULL in the List and NOT IN Returns Nothing, and they were the right thing to cite there because I was comparing plan shapes rather than claiming something was slow. The distinction is the one the skill insists on, and I would not have stated it as carefully without the prompt.
The finding it produced
The post I was writing argued that COALESCE(@param, col) in a WHERE clause blocks index seeks, which is what my style guide had said for years. That post is COALESCE in a WHERE Clause Costs You the Row Estimate.
The plans disagreed: with OPTION (RECOMPILE) both the COALESCE form and the OR @param IS NULL form produced a Clustered Index Seek, and without it both produced a scan. So the seek claim was wrong, and I could see that much from the operator names.
What I would probably have done next is conclude the two patterns are equivalent and drop the rule. The digest had already flagged the thing that made that conclusion wrong:
|
1 2 3 4 |
node 0 Clustered Index Scan: est 10,000/exec vs actual 200/exec over 1 exec(s) -> overestimated 49.8x estimate is 10.0% of table cardinality (100,000), a known fixed-guess fraction - the optimizer had no useful statistics |
The OR form estimated 200 against an actual 200. The COALESCE form estimated 10,000 against the same 200.
That is the real difference between the two patterns, and it is not visible in the operator names at all. A round fraction of table cardinality is a fingerprint: it means the estimator never got a usable predicate and fell back to a guess.[2] The skill’s instructions name that pattern explicitly, and are careful to add that you should not assert which guess it was, because the fractions vary by cardinality estimator version.
So the rule in my style guide held, with a different and more accurate reason behind it.
The other guardrails
Several more are worth repeating whether or not you use the tool.
Per-execution row math. On the inner side of a nested loop join, EstimateRows is per execution while ActualRows is the total across all executions. Comparing them directly produces enormous fake discrepancies. The digest divides for you and says so.
Self time versus cumulative time. In row mode an operator’s elapsed time includes everything beneath it, so sorting operators by raw elapsed always crowns the root node. The skill’s word for getting this wrong is that it produces “confident, precise, completely inverted answers”.
Scans are not automatically bad. A scan of a small table is optimal; a seek executed four million times is not.
Missing index requests are not DDL. The equality columns come out in arbitrary order, key order being the most important decision in index design, and the request ignores every index that already exists.
Estimated plans cannot tell you what was slow. Nothing ran. This one caught me directly: my first attempt at the optional parameter comparison used SET SHOWPLAN_XML ON, which cannot show a RECOMPILE embedding a runtime value, so all my variants looked identical. I had to re-run with actual plans to see anything real.
Why encode this as a skill
Every item above is established practice. Erik has been saying most of it publicly for years, and none of it is secret.
The value is in where it sits. These are the rules you need at the exact moment you are looking at a plan and forming an opinion, which is also the moment you are least likely to go and re-read them. Attaching them to the plan file, so they load when a plan appears, puts the caution in the path of the work.
That is a general point about this kind of tooling. The failure mode of an agent reading a query plan is not that it cannot parse XML. It is that it produces a fluent, confident answer built on the cost percentage, which is the single most misleading number available. A skill that spends most of its length on conclusions to avoid is aimed at the right problem.
It is also honest about its limits in a way I appreciated: it tells the agent to say “this is an estimated plan, so I can tell you the shape looks wrong but not what was slow” rather than inventing a bottleneck.
Getting it
The skill ships in Erik’s Claude Code plugin marketplace, and the same plugin works in GitHub Copilot CLI, which reads the same format.[1]
|
1 2 |
copilot plugin marketplace add erikdarlingdata/claude-plugins copilot plugin install sqlserver-query-plans@erikdarling |
The Claude Code equivalents are /plugin marketplace add and /plugin install with the same arguments. The extractor needs only the Python 3 standard library; without Python the skill falls back to a documented text-search approach and states plainly what it cannot determine.
If you use an AI agent for anything involving SQL Server performance, this is worth installing before the next time you paste a plan into a chat window. And if you do not, the instruction file is still worth reading as a checklist, because the list of wrong conclusions is the same whether a person or a model is the one reaching them.
Have you had an agent confidently misread a plan for you? Bluesky or LinkedIn.
References
- erikdarlingdata/claude-plugins - the Darling Data plugin marketplace containing the
sqlserver-query-plansplugin, its reference files, and the extraction script. MIT licensed. Quotations above are from the skill’s instruction file. ↩ ↩ - Cardinality Estimation (SQL Server) - Microsoft Learn. Background on how row estimates are produced and what happens when no useful statistic applies to a predicate. ↩