Move the deployment failure to the keystroke
Published .
A rollout stops halfway. The validator says a configuration value is not allowed in that region. The engineer who wrote the line did it days ago, has moved on to other work, and now has to rebuild the context, find the rule, fix one value, and wait for the pipeline again. Nothing about the mistake was hidden. It sat in the source the whole time. The system just didn't say so until deploy time.
That is the problem I want to talk about: failures that are visible in source but get reported at the last possible moment.
The rule
If the cause of a failure is visible in source, the failure belongs where the source is written: in the editor and in the build. Deploy time validation stays as a safety net. It should rarely be the first place anyone hears about a mistake they could have seen while typing.
Here is the claim in a form you can test. Take the deployment failures from your last quarter and sort them into two piles: causes that were visible in source when the change was authored, and causes that were not (capacity, a dependency outage, a race). For every class in the first pile that you move to authoring time with an analyzer, a type, or a build gate, that class should stop showing up at deploy time. If it keeps showing up, the check is wrong or incomplete, and you have learned something specific about the check.
A small, shipped example
I maintain Moq.Analyzers, a set of Roslyn analyzers for the Moq mocking library. It is a small version of the same problem.
Moq builds a mock by generating a subclass, so it can only intercept members that can be overridden. This compiles without complaint:
class SampleClass
{
int Property { get; set; }
}
var mock = new Mock<SampleClass>()
.Setup(x => x.Property);
Without an analyzer, you find out when the test runs. Moq 4 throws a NotSupportedException whose message says that "Non-overridable members (here: ...) may not be used in setup / verification expressions" (Moq source). That is a deploy time failure in miniature: the cause was visible in the code, and the report came later, somewhere else.
With Moq.Analyzers installed, the same line gets Moq1200, "Setup should be used only for overridable members," at Error severity. The build fails at the line that caused it, with the fix in the rule doc: mock an interface, or make the member virtual. Moq1200 has shipped since release 0.0.7 and is in the current release, v0.4.2 (March 11, 2026). NuGet listed 3,834,656 downloads of the package on October 17, 2026.
A mocking mistake is low stakes. The mechanism isn't. A Roslyn analyzer runs inside the compiler and the IDE, so a rule written once is enforced on every keystroke, on every build, for every team that references the package. Nobody has to remember it.
Why this is a capability gain
A diagnostic gives the author knowledge at the moment they can act on it. The engineer doesn't need to have read the right wiki page or sat through the right incident review. The rule shows up where the decision is being made, with the reason and the fix.
The same holds for coding agents. An agent that edits a project gets the compiler's diagnostics as output it can read and act on. In my last post I argued that tools should raise what an agent can do while it has them. An analyzer at Error severity is exactly that kind of instrument: it turns a rule that lived in someone's head into a fact the agent cannot miss.
What a good authoring time check needs
Moving a check earlier only helps if engineers trust it. The checks that hold up have four things in common.
- The diagnostic explains itself. It names the rule, the value it expected, and the fix. A bare "denied" is a wall, not feedback.
- There is an escape hatch. Moq1200's doc shows how to suppress one violation with
#pragmaor turn the rule off in.editorconfig. A rule that cannot be overridden on purpose gets worked around without a record. - Severity means something. Errors block. Warnings inform. If every line has a squiggle, nobody reads any of them.
- Missing input is not a pass. A check that found nothing to evaluate should say so, not report success.
Better than earlier: impossible
An analyzer catches a mistake sooner. A typed API can make the mistake impossible to write. If a deployment option only accepts the regions a service is allowed to use, the invalid value never compiles, and no check has to look for it.
Rico Mariani named this the Pit of Success in a 2003 talk to platform engineers, as Brad Abrams recorded it: "we want our customers to simply fall into winning practices by using our platform and frameworks. To the extent that we make it easy to get into trouble we fail." An analyzer at the keystroke makes trouble harder to get into. A type that refuses the wrong value makes it impossible.
Dan North put this well when he wrote about shifting testing left: it "means doing different things," and "we aren't doing that work earlier, we are setting ourselves up to never need it at all" (We Need to Talk About Testing). Analyzers are the detection half. Types are the prevention half. A good platform uses both, and keeps deploy time validation for what neither can see.
The same problem at platform scale
My team, Policy and Configuration in Microsoft Security, works on this problem at a much larger scale. Per the public job description, the team builds the shared deployment and policy capabilities that help Microsoft Security services ship safely across Azure, regions, and sovereign clouds. The work includes turning deployment, compliance, and security requirements into typed APIs, analyzers, and build gates, and moving data residency, rollout, and configuration failures from deployment time to authoring time.
The goal stated there is to make secure deployment self-service, reliable, and eventually invisible to service teams. That means clear diagnostics, safe upgrade paths, and self-service workflows, guided by support demand and adoption data. It also means a migration path from proprietary tools to standard .NET Aspire and Azure platform capabilities.
Moq1200 is one rule in one package. The interesting version of this problem is doing it for deployment policy across many services, where every rule you move to the keystroke is a class of failure service teams stop hitting.
We're hiring
I'm hiring a Principal Software Engineer in Redmond, WA to set technical direction for this work. If moving failures from deploy time to the keystroke sounds like your kind of problem, apply here: Principal Software Engineer, Policy and Configuration.
Please apply through the link rather than DMing me. If you know someone who would want this, send them the link.
