skills/resilience/circuit-breaker/SKILL.md
Use when adding circuit breaker protection against cascading failures.
npx skillsauth add faysilalshareef/dotnet-ai-kit circuit-breakerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Closed (Normal)
↓ failure ratio exceeds threshold
Open (Blocking) — all requests fail immediately with BrokenCircuitException
↓ break duration expires
Half-Open (Testing) — allows one probe request through
↓ probe succeeds → Closed
↓ probe fails → Open (reset break timer)
builder.Services.AddHttpClient("PaymentService", client =>
{
client.BaseAddress = new Uri("https://payments.{Company}.com");
})
.AddResilienceHandler("payment-resilience", pipeline =>
{
// Retry first (innermost)
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 2,
Delay = TimeSpan.FromMilliseconds(500),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true
});
// Circuit breaker wraps retry (opens when retries keep failing)
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
// Open when 50% of requests fail
FailureRatio = 0.5,
// Over this time window
SamplingDuration = TimeSpan.FromSeconds(30),
// Minimum requests before evaluating
MinimumThroughput = 10,
// Stay open for this duration before half-open
BreakDuration = TimeSpan.FromSeconds(30)
});
// Timeout per request
pipeline.AddTimeout(TimeSpan.FromSeconds(5));
});
builder.Services.AddResiliencePipeline("external-service", pipeline =>
{
pipeline.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.3,
SamplingDuration = TimeSpan.FromSeconds(60),
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
ShouldHandle = new PredicateBuilder()
.Handle<HttpRequestException>()
.Handle<TimeoutRejectedException>(),
OnOpened = args =>
{
// Log when circuit opens
var logger = args.Context
.Properties.GetValue(
new ResiliencePropertyKey<ILogger>("logger"),
null!);
logger?.LogWarning(
"Circuit opened for {Duration}. " +
"Reason: {Outcome}",
args.BreakDuration,
args.Outcome?.Exception?.Message);
return ValueTask.CompletedTask;
},
OnClosed = args =>
{
var logger = args.Context
.Properties.GetValue(
new ResiliencePropertyKey<ILogger>("logger"),
null!);
logger?.LogInformation("Circuit closed — service recovered");
return ValueTask.CompletedTask;
},
OnHalfOpened = args =>
{
var logger = args.Context
.Properties.GetValue(
new ResiliencePropertyKey<ILogger>("logger"),
null!);
logger?.LogInformation(
"Circuit half-open — testing recovery");
return ValueTask.CompletedTask;
}
});
});
public sealed class PaymentService(
IHttpClientFactory httpClientFactory,
ILogger<PaymentService> logger)
{
public async Task<Result<PaymentResponse>> ChargeAsync(
Guid orderId, decimal amount, CancellationToken ct)
{
try
{
var client = httpClientFactory.CreateClient("PaymentService");
var response = await client.PostAsJsonAsync(
"/charges",
new { orderId, amount },
ct);
response.EnsureSuccessStatusCode();
var result = await response
.Content.ReadFromJsonAsync<PaymentResponse>(ct);
return Result<PaymentResponse>.Success(result!);
}
catch (BrokenCircuitException)
{
logger.LogWarning(
"Payment service circuit is open. " +
"Order {OrderId} payment deferred", orderId);
return Result<PaymentResponse>.Failure(
Error.ServiceUnavailable("Payment.Unavailable",
"Payment service is temporarily unavailable"));
}
}
}
// Custom health check that reflects circuit state
public sealed class PaymentServiceHealthCheck(
ResiliencePipelineProvider<string> pipelineProvider)
: IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken ct = default)
{
// The circuit breaker state indicates downstream health
// If the circuit is open, report degraded
try
{
var pipeline = pipelineProvider
.GetPipeline("external-service");
// If we can get the pipeline, the circuit state is managed
return Task.FromResult(
HealthCheckResult.Healthy("Payment service reachable"));
}
catch
{
return Task.FromResult(
HealthCheckResult.Degraded("Payment service circuit open"));
}
}
}
// appsettings.json
{
"Resilience": {
"PaymentService": {
"FailureRatio": 0.5,
"SamplingDurationSeconds": 30,
"MinimumThroughput": 10,
"BreakDurationSeconds": 30
}
}
}
// Options class
public sealed class CircuitBreakerOptions
{
public double FailureRatio { get; init; } = 0.5;
public int SamplingDurationSeconds { get; init; } = 30;
public int MinimumThroughput { get; init; } = 10;
public int BreakDurationSeconds { get; init; } = 30;
}
// Usage
var cbOptions = builder.Configuration
.GetSection("Resilience:PaymentService")
.Get<CircuitBreakerOptions>()!;
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = cbOptions.FailureRatio,
SamplingDuration = TimeSpan.FromSeconds(
cbOptions.SamplingDurationSeconds),
MinimumThroughput = cbOptions.MinimumThroughput,
BreakDuration = TimeSpan.FromSeconds(
cbOptions.BreakDurationSeconds)
});
BrokenCircuitException in calling codeAddCircuitBreaker or CircuitBreakerStrategyOptionsBrokenCircuitException handlingCircuitBreakerPolicy (migration candidate)BrokenCircuitException with fallback or graceful degradation| Parameter | Conservative | Balanced | Aggressive | |-----------|-------------|----------|------------| | Failure ratio | 0.7 | 0.5 | 0.3 | | Sampling duration | 60s | 30s | 15s | | Min throughput | 20 | 10 | 5 | | Break duration | 60s | 30s | 15s |
data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.