A portable Claude Code "Stop" hook that runs Roslyn analyzers on your .NET projects, presenting issues to Claude for automatic fixing. Includes Microsoft.CodeAnalysis.NetAnalyzers, StyleCop.Analyzers, and optional community analyzers with easy toggle support.
Inspired by the HumanLayer blog post on setting up Claude Code hooks for linting.
- When Claude Code finishes responding, the Stop hook runs
- The hook checks if any
.csfiles were modified (viagit diff) - If changes exist, it runs
dotnet buildwith SARIF output enabled - Analyzer issues are parsed and formatted
- If issues are found, Claude is blocked and presented with the issues to fix
- Claude automatically continues to fix the reported issues
- dotnet CLI - .NET SDK 8.0, 9.0, or 10.0 (download)
- jq - JSON processor (install)
- bash - Shell (included on macOS/Linux)
# From your project root, clone into .claude/tools/
git clone https://github.com/johngroot/ClaudeDotNetAnalyzer .claude/tools/ClaudeDotNetAnalyzer
# Run the installer
.claude/tools/ClaudeDotNetAnalyzer/install.sh- Copy the
ClaudeDotNetAnalyzerdirectory to your project's.claude/tools/ - Make scripts executable:
chmod +x .claude/tools/ClaudeDotNetAnalyzer/hooks/*.sh chmod +x .claude/tools/ClaudeDotNetAnalyzer/lib/*.sh
Add to your project's .claude/settings.local.json:
{
"hooks": {
"Stop": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/tools/ClaudeDotNetAnalyzer/hooks/analyze-dotnet.sh",
"timeout": 60000
}
]
}
}The $CLAUDE_PROJECT_DIR variable resolves to your project root, making the path portable.
If you don’t have any of the config files already defined in your project you can copy them to its root:
cp .claude/tools/ClaudeDotNetAnalyzer/project-templates/config/Directory.Build.props ./
cp .claude/tools/ClaudeDotNetAnalyzer/project-templates/config/.editorconfig ./
cp .claude/tools/ClaudeDotNetAnalyzer/project-templates/config/stylecop.json ./Otherwise you can add the various properties to your existing files.
The Directory.Build.props file:
- Enables .NET analyzers with
latest-allanalysis level - Adds StyleCop.Analyzers as a dependency
- Enforces code style during build
Create .claude-dotnet-analyzer.json in your project root:
{
"severity_threshold": "all",
"max_issues": 50,
"ignore_rules": ["CA1707", "SA1633"],
"project_file": "src/MyProject.sln",
"analyzers": {
"idisposable_analyzers": true,
"async_fixer": true,
"meziantou_analyzer": false,
"roslynator_analyzers": false,
"sonar_analyzer": false
}
}Options:
severity_threshold:"error","warning", or"all"(default)max_issues: Maximum issues to report (default: 50)ignore_rules: Array of rule IDs to skipproject_file: Explicit path to .sln or .csproj (auto-detected if omitted)analyzers: Toggle individual analyzer packages on/off (see Analyzers Included)
Environment variables (override config file):
DOTNET_ANALYZER_SEVERITY- Severity thresholdDOTNET_ANALYZER_MAX_ISSUES- Maximum issues
You can also toggle analyzers via MSBuild properties in your .csproj or a parent Directory.Build.props:
<PropertyGroup>
<EnableIDisposableAnalyzers>false</EnableIDisposableAnalyzers>
<EnableMeziantouAnalyzer>true</EnableMeziantouAnalyzer>
</PropertyGroup>The included .editorconfig enforces these conventions:
| Element | Convention | Example |
|---|---|---|
| Classes, Methods, Properties | PascalCase | MyClass, DoSomething() |
| Private/Protected fields | _camelCase |
_myField |
| Public fields | _PascalCase |
_MyField |
| Constants | ALL_CAPS |
MAX_VALUE |
| Parameters, locals | camelCase | myParam |
| Indentation | Tabs | - |
You can customize the severity and specificity of those settings in that file
All analyzers are Roslyn-based and framework-agnostic - they work identically with .NET 8, 9, and 10. Further documentation can be found here.
| Analyzer | Rules | Description |
|---|---|---|
| Microsoft.CodeAnalysis.NetAnalyzers | CA1xxx-CA5xxx | Design, usage, and security rules (built into SDK) |
| StyleCop.Analyzers | SA0xxx-SA1xxx | Code style and documentation rules |
| Analyzer | Rules | Toggle Property | Description |
|---|---|---|---|
| IDisposableAnalyzers | IDISP001-026 | EnableIDisposableAnalyzers |
Resource leak detection (422+ GitHub stars) |
| AsyncFixer | AsyncFixer01-06 | EnableAsyncFixer |
Async/await anti-pattern detection |
| Analyzer | Rules | Toggle Property | Description |
|---|---|---|---|
| Meziantou.Analyzer | MA0001+ | EnableMeziantouAnalyzer |
139+ rules for common .NET mistakes |
| Roslynator.Analyzers | RCS1xxx+ | EnableRoslynatorAnalyzers |
200+ analyzers and code fixes |
| SonarAnalyzer.CSharp | S1xxx+ | EnableSonarAnalyzer |
Security and bug detection |
All included packages are Roslyn analyzers (PrivateAssets="All") that run at compile time via the Roslyn compiler platform. They are framework-agnostic and require no TFM-conditional references. AnalysisLevel=latest-all automatically adapts to the installed SDK version.
Note: Meziantou.Analyzer uses version 2.0.x for broader Roslyn compatibility (including Unity's older Roslyn). For pure .NET 8/9/10 projects, you can upgrade to Meziantou.Analyzer 3.x by overriding the version in your .csproj.
These analyzers are not bundled but can be added manually to your project if needed:
- AspNetCoreAnalyzers - ASP.NET Core-specific rules
- ReflectionAnalyzers - Reflection API usage validation
- WpfAnalyzers - WPF-specific rules
In your .editorconfig:
dotnet_diagnostic.CA1822.severity = none
dotnet_diagnostic.SA1633.severity = suggestionIn your code:
#pragma warning disable CA1822
// code here
#pragma warning restore CA1822Or globally in GlobalSuppressions.cs:
[assembly: SuppressMessage("Category", "CA1234:Rule", Justification = "Reason")]Edit Directory.Build.props:
<PropertyGroup>
<!-- Only errors and warnings, not suggestions -->
<AnalysisLevel>latest-recommended</AnalysisLevel>
<!-- Treat warnings as errors -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>- Verify the hook is configured in
.claude/settings.local.json - Check that scripts are executable:
chmod +x .claude/tools/ClaudeDotNetAnalyzer/hooks/*.sh .claude/tools/ClaudeDotNetAnalyzer/lib/*.sh - Run with
claude --debugto see hook execution
- Verify your project has
Directory.Build.propsor analyzer packages - Check that
.csfiles were actually modified (checkgit status) - Run
dotnet buildmanually to see if analyzers are running
- The hook uses
--no-restorefor speed - Consider increasing the timeout in settings
- Use
project_fileconfig to target a specific project instead of solution
- You can refine issue counts via your project's
.claude-dot-net-analyzer.jsonconfig - Set
max_issuesto limit output - Adjust
severity_thresholdto"warning"or"error" - Add rules to
ignore_rulesarray
ClaudeDotNetAnalyzer/
├── hooks/ # Source: hook scripts
│ └── analyze-dotnet.sh # Main entry point (Stop hook)
├── lib/ # Source: utility scripts
│ ├── parse-sarif.sh # SARIF output parser
│ └── detect-project.sh # .NET project detection
├── project-templates/ # Files to copy into your project
│ ├── config/
│ │ ├── Directory.Build.props # MSBuild config (enables analyzers)
│ │ ├── .editorconfig # Code style rules
│ │ └── stylecop.json # StyleCop settings
│ └── templates/
│ ├── claude-settings.json # Hook config template
│ └── .claude-dotnet-analyzer.json # Project config template
├── install.sh # Installer script
└── README.md