
Site Reliability Engineer
- 131 installs
- 178 repo stars
- Updated July 14, 2026
- erichowens/some_claude_skills
Define SLOs, alerting, incident response, capacity planning, and runbooks so production SaaS and API services stay reliable, observable, and recoverable under real traffic and failure modes.
About
Embodies site reliability engineering practices for running production services: observability stacks, on-call readiness, deployment safety, infrastructure tuning, and blameless postmortems aimed at reducing downtime and toil for API-first and CLI-backed SaaS platforms.
- SLO and error-budget design
- Alert noise reduction
- Incident response playbooks
- Capacity and scaling reviews
- Postmortem-driven hardening
Site Reliability Engineer by the numbers
- 131 all-time installs (skills.sh)
- Ranked #492 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/erichowens/some_claude_skills --skill site-reliability-engineerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 131 |
|---|---|
| repo stars | ★ 178 |
| Last updated | July 14, 2026 |
| Repository | erichowens/some_claude_skills ↗ |
What it does
Define SLOs, alerting, incident response, capacity planning, and runbooks so production SaaS and API services stay reliable, observable, and recoverable under real traffic and failure modes.
Files
Site Reliability Engineer
Expert in Docusaurus build health, MDX validation, and deployment safety for the Claude Skills showcase website. Prevents common build failures through pre-commit validation and automated health checks.
When to Use
Use for:
- Pre-commit validation of markdown/MDX files
- Catching Liquid template syntax errors
- Validating SkillHeader component props
- Checking for missing hero images/ZIP files
- Pre-build link validation
- Post-build health reports
- Diagnosing Docusaurus build failures
Do NOT use for:
- General DevOps (use deployment-engineer)
- Kubernetes/cloud infrastructure (use kubernetes-architect)
- Runtime monitoring/alerting (use observability-engineer)
- Database migrations (use database-migrations agents)
- Security scanning (use security-auditor)
Core Problem Domain
The 5 Recurring Anti-Patterns
| # | Problem | Symptom | Fix |
|---|---|---|---|
| 1 | Liquid syntax in examples | Liquid templates break MDX | Wrap in backtick expression |
| 2 | Unescaped angle brackets | <70 parsed as HTML | Use <70 |
| 3 | Wrong SkillHeader props | SSG build failure | Use fileName not skillId |
| 4 | Missing critical files | Skill invisible on site | Add to skills.ts |
| 5 | Cache corruption | Phantom errors | Clear .docusaurus, build |
Quick Start
Install Hooks (One-Time)
npm run install-hooksManual Validation
npm run validate:liquid # Liquid syntax
npm run validate:brackets # Angle brackets
npm run validate:props # SkillHeader props
npm run validate:all # All checksClear Cache (When Stuck)
rm -rf .docusaurus build node_modules/.cache
npm run buildPre-Commit Validation
The pre-commit hook automatically: 1. Liquid syntax - Scans for double-brace templates outside code blocks 2. Angle brackets - Finds <digit patterns 3. SkillHeader props - Validates component usage 4. Required files - Checks hero images, ZIPs exist
Speed: Under 5 seconds for typical commits
Expert vs Novice Approach
| Novice | Expert |
|---|---|
| Runs full build to check | Pre-commit catches 90% in 5 seconds |
| Manual cache clearing | Auto-detect cache issues |
| Ignores warnings | Zero-tolerance for broken links |
| Simple regex validation | Context-aware (skips code blocks) |
Anti-Patterns
Anti-Pattern: Full Build for Validation
What it looks like: npm run build to check for errors Why wrong: Minutes vs seconds, slow feedback Instead: npm run validate:all (under 30 seconds)
Anti-Pattern: Ignoring Build Warnings
What it looks like: "Build succeeded, ship it!" (ignoring warnings) Why wrong: Broken links = poor UX, tech debt Instead: Post-build validation fails on warnings
Anti-Pattern: Naive Regex Validation
What it looks like: /\{\{.*?\}\}/ (matches in code blocks too) Why wrong: False positives in code examples Instead: Track code block state, skip protected regions
Scripts (in scripts/ folder)
| Script | Purpose |
|---|---|
validate-liquid.js | Detect unescaped Liquid syntax |
validate-brackets.js | Detect unescaped angle brackets |
validate-skill-props.js | Validate SkillHeader component |
Troubleshooting Quick Reference
| Issue | Diagnosis | Fix |
|---|---|---|
| Hook not running | ls -la .git/hooks/pre-commit | chmod +x or reinstall |
| False positives | Pattern in code block | Check ``` markers |
| Slow validation | time npm run validate:all | Optimize glob patterns |
Success Metrics
After installing hooks:
- Build failure rate: 15% → under 2%
- Time to diagnose errors: 10 min → under 1 min
- Validation speed: Under 30 seconds
Reference Files
references/validation-logic.md- Context-aware detection patternsreferences/ci-cd-integration.md- GitHub Actions, health reportsscripts/- Working validation scripts
---
Prevents: Liquid errors | Angle bracket failures | Prop mismatches | Missing assets | Broken links
Use with: skill-documentarian (sync) | docusaurus-expert (advanced config)
Changelog
[2.0.0] - 2024-01-XX
Changed
- BREAKING: Restructured from monolithic 789-line file to progressive disclosure architecture
- Fixed frontmatter format:
tools:→allowed-tools:(comma-separated) - Added NOT clause to description for precise activation boundaries
- Reduced SKILL.md from 789 lines to 132 lines (83% reduction)
Added
references/ci-cd-integration.md- GitHub Actions workflows, Docker configsreferences/monitoring-alerting.md- Prometheus, Grafana dashboards, alert rulesreferences/incident-response.md- Runbooks, post-mortem templates, escalation- Anti-patterns section with "What it looks like / Why wrong / Instead" format
- Quick reference tables for SLO targets and incident severity
Removed
- Inline YAML/Python examples (moved to references)
- Verbose incident response procedures (condensed to decision trees)
- Redundant monitoring configurations
Migration Guide
Reference files are now in /references/ directory. Import patterns:
- CI/CD templates →
references/ci-cd-integration.md - Alert configurations →
references/monitoring-alerting.md - Incident runbooks →
references/incident-response.md
CI/CD Integration Reference
Automated build health for GitHub Actions and deployment pipelines.
GitHub Actions Workflow
File: .github/workflows/build-health.yml
name: Build Health Check
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: cd website && npm ci
- name: Run pre-build validation
run: cd website && npm run validate:all
- name: Build
run: cd website && npm run build
- name: Post-build health check
run: cd website && npm run postbuild
- name: Upload health report
uses: actions/upload-artifact@v3
with:
name: build-health
path: website/.build-health.jsonHealth Report Schema
File: website/.build-health.json (generated post-build)
{
"timestamp": "2025-11-26T05:00:00Z",
"build": {
"success": true,
"duration_ms": 45328,
"bundle_size_mb": 8.2,
"warnings": 3,
"errors": 0
},
"broken_links": {
"count": 2,
"details": [
{
"file": "docs/skills/cv_creator.md",
"line": 393,
"target": "/planning/cv-creator-architecture.md",
"type": "internal"
}
]
},
"skills": {
"total": 40,
"with_hero_images": 40,
"with_zips": 40,
"in_skills_ts": 40
},
"recommendation": "Fix 2 broken internal links before deployment"
}npm Scripts for CI
Add to website/package.json:
{
"scripts": {
"prebuild": "npm run validate:all",
"postbuild": "node scripts/post-build-health-check.js",
"validate:liquid": "node scripts/validate-liquid.js 'website/docs/**/*.md'",
"validate:brackets": "node scripts/validate-brackets.js 'website/docs/**/*.md'",
"validate:props": "node scripts/validate-skill-props.js 'website/docs/skills/*.md'",
"validate:links": "node scripts/validate-internal-links.js",
"validate:all": "npm run validate:liquid && npm run validate:brackets && npm run validate:props"
}
}Thresholds Configuration
File: website/.site-reliability.config.json
{
"thresholds": {
"bundleSizeMB": 10,
"brokenLinksMax": 0,
"imageMaxSizeKB": 1024,
"buildTimeoutMinutes": 10
},
"validation": {
"liquid": { "enabled": true, "autoFix": false },
"brackets": { "enabled": true, "autoFix": true },
"skillProps": {
"enabled": true,
"requiredProps": ["skillName", "fileName", "description"],
"deprecatedProps": ["difficulty", "category", "tags"]
}
}
}CI Success Criteria
| Metric | Threshold | Action if Exceeded |
|---|---|---|
| Bundle size | 10MB | Fail build |
| Broken links | 0 | Fail build |
| Build time | 10 min | Warn |
| Hero images | 100% coverage | Warn |
Deployment Gate Script
// scripts/deployment-gate.js
const health = require('./.build-health.json');
const failures = [];
if (health.broken_links.count > 0) {
failures.push(`${health.broken_links.count} broken links`);
}
if (health.build.bundle_size_mb > 10) {
failures.push(`Bundle size ${health.build.bundle_size_mb}MB exceeds 10MB`);
}
if (health.skills.with_hero_images < health.skills.total) {
failures.push(`Missing hero images: ${health.skills.total - health.skills.with_hero_images}`);
}
if (failures.length > 0) {
console.error('❌ Deployment blocked:');
failures.forEach(f => console.error(` - ${f}`));
process.exit(1);
}
console.log('✅ Deployment gate passed');
process.exit(0);Validation Logic Reference
Context-aware detection patterns used by validation scripts.
Core Principle: Context-Aware Detection
Simple regex fails because it doesn't understand MDX context. Always: 1. Track code block state (``` markers) 2. Track frontmatter state (---` markers) 3. Only validate outside protected regions
Liquid Syntax Detection
Problem: Double-brace template syntax in Vue/Handlebars examples gets interpreted as Liquid.
Context-Aware Logic:
function validateLiquid(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const errors = [];
let inCodeBlock = false;
let inFrontmatter = false;
lines.forEach((line, idx) => {
// Track code blocks
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock;
return;
}
// Track frontmatter
if (line.trim() === '---') {
inFrontmatter = !inFrontmatter;
return;
}
// Skip if in protected region
if (inCodeBlock || inFrontmatter) return;
// Check for unescaped Liquid syntax
const liquidMatch = line.match(/\{\{[^`].*?\}\}/);
if (liquidMatch && !line.includes('{`{{')) {
errors.push({
line: idx + 1,
column: line.indexOf(liquidMatch[0]) + 1,
text: liquidMatch[0],
suggestion: `{\\`${liquidMatch[0]}\\`}`
});
}
});
return errors;
}Fix Pattern: Wrap in MDX expression syntax using backtick expressions
Angle Bracket Detection
Problem: <70 parsed as incomplete HTML tag, breaks MDX.
Context-Aware Logic:
function validateBrackets(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const errors = [];
let inCodeBlock = false;
lines.forEach((line, idx) => {
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock;
return;
}
if (inCodeBlock) return;
// Check for unescaped < followed by digit
const lessThanMatch = line.match(/<(\d+)/);
const greaterThanMatch = line.match(/>(\d+)/);
if (lessThanMatch && !line.includes('<')) {
errors.push({
line: idx + 1,
text: lessThanMatch[0],
fix: lessThanMatch[0].replace('<', '<')
});
}
if (greaterThanMatch && !line.includes('>')) {
errors.push({
line: idx + 1,
text: greaterThanMatch[0],
fix: greaterThanMatch[0].replace('>', '>')
});
}
});
return errors;
}Fix Pattern: Replace < with < and > with >
SkillHeader Prop Validation
Problem: Wrong prop names cause SSG build failure.
Validation Logic:
function validateSkillHeader(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const errors = [];
// Find SkillHeader component
const headerMatch = content.match(/<SkillHeader[\s\S]*?\/>/);
if (!headerMatch) return errors;
const headerText = headerMatch[0];
const lines = content.split('\n');
const lineNum = lines.findIndex(l => l.includes('<SkillHeader')) + 1;
// Check for wrong prop name
if (headerText.includes('skillId=')) {
errors.push({
line: lineNum,
issue: 'Uses "skillId" instead of "fileName"',
fix: 'Change skillId to fileName'
});
}
// Check for deprecated props
['difficulty', 'category', 'tags'].forEach(prop => {
if (headerText.includes(`${prop}=`)) {
errors.push({
line: lineNum,
issue: `Uses deprecated "${prop}" prop`,
fix: 'Remove - only use: skillName, fileName, description'
});
}
});
// Check for required props
if (!headerText.includes('skillName=')) {
errors.push({ line: lineNum, issue: 'Missing required "skillName"' });
}
if (!headerText.includes('fileName=')) {
errors.push({ line: lineNum, issue: 'Missing required "fileName"' });
}
return errors;
}Current SkillHeader Interface
interface SkillHeaderProps {
skillName: string; // Required: Display name
fileName: string; // Required: matches skill folder (underscores)
description: string; // Required: from SKILL.md frontmatter
}Deprecated props (remove if found): difficulty, category, tags
#!/usr/bin/env node
const fs = require('fs');
function validateBrackets(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const errors = [];
let inCodeBlock = false;
lines.forEach((line, idx) => {
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock;
return;
}
if (inCodeBlock) return;
// Check for unescaped < followed by digit or > followed by digit
const lessThanMatch = line.match(/<(\d+)/);
const greaterThanMatch = line.match(/>(\d+)/);
if (lessThanMatch && !line.includes('<')) {
errors.push({
line: idx + 1,
text: lessThanMatch[0],
fix: lessThanMatch[0].replace('<', '<')
});
}
if (greaterThanMatch && !line.includes('>')) {
errors.push({
line: idx + 1,
text: greaterThanMatch[0],
fix: greaterThanMatch[0].replace('>', '>')
});
}
});
return errors;
}
const files = process.argv.slice(2);
let totalErrors = 0;
if (files.length === 0) {
console.error('Usage: validate-brackets.js <file1.md> [file2.md...]');
process.exit(1);
}
files.forEach(file => {
if (!fs.existsSync(file)) {
console.error(`File not found: ${file}`);
return;
}
const errors = validateBrackets(file);
if (errors.length > 0) {
console.error(`❌ ${file}:`);
errors.forEach(err => {
console.error(` Line ${err.line}: "${err.text}" → "${err.fix}"`);
});
totalErrors += errors.length;
}
});
if (totalErrors > 0) {
console.error(`\n❌ Found ${totalErrors} unescaped angle bracket(s)`);
console.error(`\nReplace < with < and > with > in markdown text`);
process.exit(1);
} else {
console.log('✅ No unescaped angle brackets found');
process.exit(0);
}
#!/usr/bin/env node
const fs = require('fs');
const glob = require('glob');
// Detect unescaped Liquid template syntax in MDX files
function validateLiquid(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const errors = [];
let inCodeBlock = false;
let inFrontmatter = false;
lines.forEach((line, idx) => {
// Track code blocks
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock;
return;
}
// Track frontmatter
if (line.trim() === '---') {
inFrontmatter = !inFrontmatter;
return;
}
// Skip if in code block or frontmatter
if (inCodeBlock || inFrontmatter) return;
// Check for unescaped Liquid syntax
const liquidMatch = line.match(/\{\{[^`].*?\}\}/);
if (liquidMatch && !line.includes('{`{{')) {
errors.push({
line: idx + 1,
column: line.indexOf(liquidMatch[0]) + 1,
text: liquidMatch[0],
suggestion: `{\\`${liquidMatch[0]}\\`}`
});
}
});
return errors;
}
// Process files
const files = process.argv.slice(2);
let totalErrors = 0;
if (files.length === 0) {
console.error('Usage: validate-liquid.js <file1.md> [file2.md...]');
process.exit(1);
}
files.forEach(file => {
if (!fs.existsSync(file)) {
console.error(`File not found: ${file}`);
return;
}
const errors = validateLiquid(file);
if (errors.length > 0) {
console.error(`❌ ${file}:`);
errors.forEach(err => {
console.error(` Line ${err.line}:${err.column}: ${err.text}`);
console.error(` Fix: ${err.suggestion}`);
});
totalErrors += errors.length;
}
});
if (totalErrors > 0) {
console.error(`\n❌ Found ${totalErrors} Liquid syntax error(s)`);
console.error(`\nTo fix automatically, wrap {{ ... }} in MDX expression: {\\`{{ ... }}\\`}`);
process.exit(1);
} else {
console.log('✅ No Liquid syntax errors found');
process.exit(0);
}
#!/usr/bin/env node
const fs = require('fs');
function validateSkillHeader(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const errors = [];
// Find SkillHeader component usage
const headerMatch = content.match(/<SkillHeader[\s\S]*?\/>/);
if (!headerMatch) return errors;
const headerText = headerMatch[0];
const lines = content.split('\n');
const lineNum = lines.findIndex(l => l.includes('<SkillHeader')) + 1;
// Check for correct prop: fileName (not skillId)
if (headerText.includes('skillId=')) {
errors.push({
line: lineNum,
issue: 'Uses "skillId" prop instead of "fileName"',
fix: 'Change skillId="..." to fileName="..."'
});
}
// Check for removed props (difficulty, category, tags)
const deprecatedProps = ['difficulty', 'category', 'tags'];
deprecatedProps.forEach(prop => {
if (headerText.includes(`${prop}=`)) {
errors.push({
line: lineNum,
issue: `Uses deprecated "${prop}" prop`,
fix: `Remove ${prop} prop (only use: skillName, fileName, description)`
});
}
});
// Check for required props
if (!headerText.includes('skillName=')) {
errors.push({
line: lineNum,
issue: 'Missing required "skillName" prop'
});
}
if (!headerText.includes('fileName=')) {
errors.push({
line: lineNum,
issue: 'Missing required "fileName" prop'
});
}
return errors;
}
const files = process.argv.slice(2);
let totalErrors = 0;
if (files.length === 0) {
console.error('Usage: validate-skill-props.js <file1.md> [file2.md...]');
process.exit(1);
}
files.forEach(file => {
if (!fs.existsSync(file)) {
console.error(`File not found: ${file}`);
return;
}
const errors = validateSkillHeader(file);
if (errors.length > 0) {
console.error(`❌ ${file}:`);
errors.forEach(err => {
console.error(` Line ${err.line}: ${err.issue}`);
if (err.fix) console.error(` Fix: ${err.fix}`);
});
totalErrors += errors.length;
}
});
if (totalErrors > 0) {
console.error(`\n❌ Found ${totalErrors} SkillHeader prop error(s)`);
process.exit(1);
} else {
console.log('✅ SkillHeader props validated successfully');
process.exit(0);
}