skills/observability/health-checks/SKILL.md
Use when adding health check endpoints, Kubernetes probes, or health check UI.
npx skillsauth add faysilalshareef/dotnet-ai-kit health-checksInstall 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.
/health/live — liveness: is the process running? (no dependency checks)/health/ready — readiness: can the service handle requests? (all deps checked)/health — comprehensive: full status with details for monitoring dashboards// Program.cs
builder.Services.AddHealthChecks()
// Database
.AddSqlServer(
connectionString: builder.Configuration
.GetConnectionString("Default")!,
name: "sqlserver",
tags: ["ready", "db"])
// Redis cache
.AddRedis(
redisConnectionString: builder.Configuration
.GetConnectionString("Redis")!,
name: "redis",
tags: ["ready", "cache"])
// HTTP dependency
.AddUrlGroup(
uri: new Uri(builder.Configuration["PaymentService:Url"]!),
name: "payment-service",
tags: ["ready", "external"])
// Custom business check
.AddCheck<OrderProcessingHealthCheck>(
name: "order-processing",
tags: ["ready", "business"]);
// Liveness — no dependency checks
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false // always healthy if process is running
});
// Readiness — check all dependencies
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = WriteDetailedResponse
});
// Full status — all checks with details
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = WriteDetailedResponse
});
static Task WriteDetailedResponse(
HttpContext context, HealthReport report)
{
context.Response.ContentType = "application/json";
var response = new
{
status = report.Status.ToString(),
duration = report.TotalDuration.TotalMilliseconds,
timestamp = DateTimeOffset.UtcNow,
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
duration = e.Value.Duration.TotalMilliseconds,
description = e.Value.Description,
tags = e.Value.Tags,
error = e.Value.Exception?.Message,
data = e.Value.Data.Count > 0 ? e.Value.Data : null
})
};
return context.Response.WriteAsJsonAsync(response);
}
public sealed class OrderProcessingHealthCheck(AppDbContext db)
: IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken ct = default)
{
var stuckOrders = await db.Orders
.CountAsync(o =>
o.Status == OrderStatus.Processing &&
o.UpdatedAt < DateTimeOffset.UtcNow.AddMinutes(-30),
ct);
var data = new Dictionary<string, object>
{
["stuckOrderCount"] = stuckOrders
};
return stuckOrders switch
{
0 => HealthCheckResult.Healthy(
"No stuck orders", data),
< 5 => HealthCheckResult.Degraded(
$"{stuckOrders} orders stuck > 30 min", data),
_ => HealthCheckResult.Unhealthy(
$"{stuckOrders} orders stuck > 30 min", data: data)
};
}
}
public sealed class MessageQueueHealthCheck(
IMessageQueueClient queueClient) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken ct = default)
{
var depth = await queueClient.GetQueueDepthAsync(ct);
var data = new Dictionary<string, object>
{
["queueDepth"] = depth
};
return depth switch
{
< 100 => HealthCheckResult.Healthy(
$"Queue depth: {depth}", data),
< 1000 => HealthCheckResult.Degraded(
$"Queue depth high: {depth}", data),
_ => HealthCheckResult.Unhealthy(
$"Queue depth critical: {depth}", data: data)
};
}
}
# deployment.yaml
spec:
containers:
- name: order-service
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
startupProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
// Install: AspNetCore.HealthChecks.UI
// Install: AspNetCore.HealthChecks.UI.InMemory.Storage
builder.Services.AddHealthChecksUI(options =>
{
options.SetEvaluationTimeInSeconds(30);
options.MaximumHistoryEntriesPerEndpoint(100);
options.AddHealthCheckEndpoint("API", "/health");
})
.AddInMemoryStorage();
app.MapHealthChecksUI(options =>
{
options.UIPath = "/health-ui";
});
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" />
<PackageReference Include="AspNetCore.HealthChecks.Uris" />
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" />
<!-- Optional UI -->
<PackageReference Include="AspNetCore.HealthChecks.UI" />
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" />
AddHealthChecks() in Program.csMapHealthChecks endpoint mappingIHealthCheck implementationsAspNetCore.HealthChecks.* packages in .csprojProgram.cs/health/live, /health/ready, /health| Endpoint | Purpose | Checks | Probe |
|----------|---------|--------|-------|
| /health/live | Is process alive? | None | Liveness |
| /health/ready | Can handle requests? | DB, cache, deps | Readiness |
| /health | Full status | All checks | Dashboard |
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.