Synercoding.ClaudeApprover
1.0.0-alpha009
See the version list below for details.
dotnet add package Synercoding.ClaudeApprover --version 1.0.0-alpha009
NuGet\Install-Package Synercoding.ClaudeApprover -Version 1.0.0-alpha009
<PackageReference Include="Synercoding.ClaudeApprover" Version="1.0.0-alpha009" />
<PackageVersion Include="Synercoding.ClaudeApprover" Version="1.0.0-alpha009" />
<PackageReference Include="Synercoding.ClaudeApprover" />
paket add Synercoding.ClaudeApprover --version 1.0.0-alpha009
#r "nuget: Synercoding.ClaudeApprover, 1.0.0-alpha009"
#:package Synercoding.ClaudeApprover@1.0.0-alpha009
#addin nuget:?package=Synercoding.ClaudeApprover&version=1.0.0-alpha009&prerelease
#tool nuget:?package=Synercoding.ClaudeApprover&version=1.0.0-alpha009&prerelease
Synercoding.ClaudeApprover
A .NET library for building Claude Code PreToolUse hook approver scripts. It provides a polymorphic approval system where you subclass BaseApprover and override per-tool handler methods to allow, deny, or ask for each tool invocation. Communication with Claude Code happens via JSON over stdin/stdout.
How It Works
Claude Code supports hooks that run before a tool is executed. This library lets you write a .NET script that acts as a PreToolUse hook, giving you programmatic control over which operations Claude Code is allowed to perform.
The core approval flow:
- Claude Code sends a
ToolInputJSON object via stdin - The library deserializes it into a typed
IToolInput(e.g.BashInput,EditInput,WriteInput) BaseApprover.Handle()dispatches to the appropriate per-tool virtual method- Your handler returns a
PreToolUseOutputwith aPermissionDecision(Allow,Deny, orAsk) and an optional reason - The output is serialized back to Claude Code via stdout
Getting Started
The approver script uses dotnet run with C# file-based programs, which requires the .NET 10 SDK or later installed on the machine running the hook.
Install the NuGet package:
dotnet add package Synercoding.ClaudeApprover
The library targets net10.0.
Usage
1. Create an approver script
Create a C# file (e.g. .claude/hooks/Approver.cs) that uses the library. You can use the built-in InsideProjectAllowedApprover which restricts all file and bash operations to within the project root, or subclass BaseApprover for full control.
A working example is available at .claude/hooks/Approver.cs:
#:sdk Microsoft.NET.Sdk
#:package Synercoding.ClaudeApprover@*
using Synercoding.ClaudeApprover;
using Synercoding.ClaudeApprover.Converters;
using Synercoding.ClaudeApprover.Input;
using Synercoding.ClaudeApprover.Output;
using System.Text.Json;
var (json, input) = InputProcessor.Process(Console.OpenStandardInput());
if (input is null)
{
Console.Error.WriteLine("Input could not be parsed...");
return 1;
}
var approver = new InsideProjectAllowedApprover();
var output = approver.Handle(input);
if (output is not null)
{
var outputJson = JsonSerializer.Serialize(output, ToolOutputJsonContext.Default.PreToolUseOutput);
Console.WriteLine(outputJson);
}
return 0;
2. Configure the hook
Create a .claude/hooks/run-approver.cjs wrapper script that invokes the approver via dotnet run:
const { execFileSync } = require("child_process");
const { join } = require("path");
const workDir = join(process.env.CLAUDE_PROJECT_DIR, ".claude", "hooks");
try {
execFileSync("dotnet", ["run", "-v", "q", "./Approver.cs"], {
cwd: workDir,
stdio: "inherit",
});
} catch (err) {
process.exit(err.status ?? 1);
}
Then add a PreToolUse hook in your .claude/settings.json that runs the wrapper:
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node -e \"require(require('path').join(process.env.CLAUDE_PROJECT_DIR,'.claude','hooks','run-approver.cjs'))\""
}
]
}
]
}
}
The Node.js wrapper ensures the hook works correctly cross-platform. Windows and Linux have different syntax for referencing environment variables in shell commands (%VAR% vs $VAR), which is needed to resolve the hook script path via CLAUDE_PROJECT_DIR. Using a Node.js wrapper with process.env and path.join avoids this issue entirely. The matcher is set to * so the hook runs for every tool invocation. The -v q flag suppresses MSBuild output so only the JSON response is written to stdout.
If you only use the hook in a .claude/settings.local.json (which is not checked into source control and thus platform-specific), you can skip the wrapper and invoke dotnet run directly:
On Linux / macOS:
"command": "cd \"$CLAUDE_PROJECT_DIR\"/.claude/hooks && dotnet run -v q ./Approver.cs"
On Windows:
"command": "cd /d \"%CLAUDE_PROJECT_DIR%\\.claude\\hooks\" && dotnet run -v q Approver.cs"
3. Customize approval logic
To implement custom rules, subclass BaseApprover and override the handler methods you need:
public class MyApprover : BaseApprover
{
public override PreToolUseOutput? Handle(ToolInput input, WriteInput write)
{
if (write.FilePath.EndsWith(".csproj"))
return Deny("Modifying project files is not allowed.");
return Allow();
}
public override PreToolUseOutput? Handle(ToolInput input, BashInput bash)
{
return Ask(); // Always ask the user for bash commands
}
}
Each handler can return:
Allow()- permit the operationDeny(reason)- block the operation with an explanationAsk(reason)- prompt the user for confirmationnull- no opinion (the default)
Built-in Approvers
InsideProjectAllowedApprover
The included InsideProjectAllowedApprover enforces that all file operations (read, edit, write) and bash commands stay within the project root directory. It also prevents access to .git directories and includes a bash command parser with per-command approval via the CommandApprovers dictionary.
The set of recognized bash commands can be extended by adding entries to the CommandApprovers dictionary property. Any command not in the dictionary will default to Ask. For example:
var approver = new InsideProjectAllowedApprover();
approver.CommandApprovers["dotnet"] = InsideProjectAllowedApprover.AllowCommand;
Additional Directories
By default, all file and bash operations are restricted to the project root. You can allow access to additional directories (e.g. sibling projects) using the AdditionalDirectories property. Paths can be absolute, relative to the project root, or use ~/ to reference the user's home directory:
var approver = new InsideProjectAllowedApprover();
approver.AdditionalDirectories.Add("../sister-project");
approver.AdditionalDirectories.Add("/opt/shared-libs");
approver.AdditionalDirectories.Add("~/.claude"); // allow access to Claude profile directory
The approver can also import additional directories from Claude's own settings files. When ImportAdditionalDirsFromClaude is true (the default), it reads the additionalDirectories JSON array from .claude/settings.json and .claude/settings.local.json:
{
"additionalDirectories": ["../sister-project", "../shared-utils"]
}
To disable importing from Claude settings:
approver.ImportAdditionalDirsFromClaude = false;
MCP Approvers
You can also add custom logic for specific mcp servers:
var approver = new InsideProjectAllowedApprover();
approver.McpApprovers["playwright"] = (tool, input) => BaseApprover.Allow();
Project Root Detection
BaseApprover resolves the project root by checking (in order):
CLAUDE_PROJECT_DIRenvironment variable- Directory containing a
.claudefolder - Directory containing a
.slnor.slnxfile
Build & Test
dotnet build
dotnet test
License
See LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-alpha010 | 57 | 3/30/2026 |
| 1.0.0-alpha009 | 52 | 3/27/2026 |
| 1.0.0-alpha008 | 37 | 3/26/2026 |
| 1.0.0-alpha007 | 35 | 3/26/2026 |
| 1.0.0-alpha006 | 43 | 3/24/2026 |
| 1.0.0-alpha005 | 39 | 3/24/2026 |
| 1.0.0-alpha004 | 44 | 3/23/2026 |
| 1.0.0-alpha003 | 54 | 3/17/2026 |
| 1.0.0-alpha002 | 62 | 2/2/2026 |
| 1.0.0-alpha001 | 55 | 2/1/2026 |
Added comment skipping support to the bash command parser, so comments no longer cause the approver to ask the user to confirm the bash command.