Install any skill in seconds. Free to start, no credit card required.
Get Started Free →🛡️ AI-powered error handling assistant that transforms fragile code into resilient systems. Automatically generate comprehensive error handling, recovery strategies, and graceful degradation. Because every production system deserves bulletproof error handling.
.claude/skills/leoyeai-cuihua-error-handler/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 317% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 422% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 385% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 221% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 211% | 0% |
> Turn fragile code into production-ready, resilient systems.
An intelligent error handling assistant that automatically:
The harsh reality:
cuihua-error-handler fixes all of this.
Tell your OpenClaw agent: > "Check error handling coverage in src/"
The agent will:
> "Add error handling to getUserById in api/users.js"
The agent will:
> "Add circuit breaker to payment service"
The agent will:
Automatically finds missing error handling:
javascript// ❌ BEFORE - Fragile code async function getUserById(id) { const res = await fetch(`/api/users/${id}`); return res.json(); } // 🔍 DETECTED ISSUES: // - No error handling for network failures // - No handling for non-200 responses // - No handling for invalid JSON // - No logging for debugging
Generates production-ready error handling:
javascript// ✅ AFTER - Bulletproof code class UserServiceError extends Error { constructor(message, options = {}) { super(message); this.name = 'UserServiceError'; this.statusCode = options.statusCode; this.originalError = options.cause; } } async function getUserById(id) { try { // Validation if (!id || typeof id !== 'string') { throw new UserServiceError('Invalid user ID', { statusCode: 400 }); } // Network request with timeout const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const res = await fetch(`/api/users/${id}`, { signal: controller.signal }); clearTimeout(timeout); // HTTP error handling if (!res.ok) { if (res.status === 404) { throw new UserServiceError(`User ${id} not found`, { statusCode: 404 }); } if (res.status >= 500) { throw new UserServiceError('Server error, please retry', { statusCode: 502 }); } throw new UserServiceError(`HTTP ${res.status}`, { statusCode: res.status }); } // JSON parsing with error handling let data; try { data = await res.json(); } catch (parseError) { throw new UserServiceError('Invalid response format', { statusCode: 502, cause: parseError }); } return data; } catch (error) { // Network errors (timeout, connection refused) if (error.name === 'AbortError') { logger.error('getUserById timeout', { id, timeout: 5000 }); throw new UserServiceError('Request timeout', { statusCode: 504, cause: error }); } if (error.message.includes('fetch failed')) { logger.error('getUserById network error', { id, error: error.message }); throw new UserServiceError('Network error', { statusCode: 503, cause: error }); } // Re-throw UserServiceError if (error instanceof UserServiceError) { logger.error('getUserById failed', { id, error: error.message }); throw error; } // Unexpected errors logger.error('getUserById unexpected error', { id, error }); throw new UserServiceError('Internal error', { statusCode: 500, cause: error }); } }
Smart retry with exponential backoff:
javascriptasync function retryWithBackoff(fn, options = {}) { const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000, backoffFactor = 2, shouldRetry = (error) => true } = options; let lastError; let delay = initialDelay; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error; // Check if we should retry if (attempt === maxRetries || !shouldRetry(error)) { throw error; } // Wait before retry logger.warn(`Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`, { error: error.message }); await new Promise(resolve => setTimeout(resolve, delay)); // Exponential backoff delay = Math.min(delay * backoffFactor, maxDelay); } } throw lastError; } // Usage async function fetchUserWithRetry(id) { return retryWithBackoff( () => getUserById(id), { maxRetries: 3, shouldRetry: (error) => { // Retry on network errors and 5xx return error.statusCode >= 500 || error.name === 'NetworkError'; } } ); }
Prevent cascading failures:
javascriptclass CircuitBreaker { constructor(fn, options = {}) { this.fn = fn; this.failureThreshold = options.failureThreshold || 5; this.resetTimeout = options.resetTimeout || 60000; this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN this.failureCount = 0; this.nextAttempt = Date.now(); } async execute(...args) { if (this.state === 'OPEN') { if (Date.now() < this.nextAttempt) { throw new Error('Circuit breaker is OPEN'); } // Try to recover this.state = 'HALF_OPEN'; } try { const result = await this.fn(...args); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failureCount = 0; if (this.state === 'HALF_OPEN') { this.state = 'CLOSED'; logger.info('Circuit breaker recovered'); } } onFailure() { this.failureCount++; if (this.failureCount >= this.failureThreshold) { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.resetTimeout; logger.error('Circuit breaker opened', { failureCount: this.failureCount, resetTimeout: this.resetTimeout }); } } } // Usage const getUserBreaker = new CircuitBreaker(getUserById, { failureThreshold: 5, resetTimeout: 60000 }); async function fetchUserSafely(id) { try { return await getUserBreaker.execute(id); } catch (error) { if (error.message === 'Circuit breaker is OPEN') { // Return cached data or default return getCachedUser(id) || { id, name: 'Unknown', error: true }; } throw error; } }
Fallback to cached/default data:
javascriptasync function getUserWithFallback(id) { try { // Try primary source return await getUserById(id); } catch (error) { logger.warn('Primary source failed, trying fallback', { id, error: error.message }); try { // Try cache const cached = await cache.get(`user:${id}`); if (cached) { logger.info('Returned cached user', { id }); return { ...cached, _cached: true }; } } catch (cacheError) { logger.error('Cache also failed', { id, error: cacheError.message }); } // Return default user logger.warn('Returning default user', { id }); return { id, name: 'Guest User', _default: true, _error: error.message }; } }
Comprehensive coverage reporting:
🛡️ Error Handling Coverage Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📁 Files analyzed: 42
🔍 Functions analyzed: 156
- Async functions: 89
- Sync functions: 67
📊 Overall coverage: 68%
❌ Missing error handling (28 functions):
Critical (needs immediate attention):
1. api/payment.js:45 - processPayment()
Risk: High - Financial transaction
Recommendation: Add try/catch + transaction rollback
2. api/auth.js:23 - verifyToken()
Risk: High - Security critical
Recommendation: Add error handling + logging
3. db/users.js:78 - updateUserProfile()
Risk: Medium - Data mutation
Recommendation: Add validation + rollback
⚠️ Weak error handling (12 functions):
1. api/users.js:12 - getUserById()
Issue: Generic catch block without specific handling
Recommendation: Add error type differentiation
2. services/email.js:34 - sendWelcomeEmail()
Issue: Swallowed errors (empty catch block)
Recommendation: Add logging + alerting
💡 Recommendations:
- Add error handling to 28 unprotected functions
- Improve 12 weak error handlers
- Target coverage: 90%+ for production readinessUser: "Add error handling to POST /api/orders"
Agent:
javascript// ✅ Generated comprehensive error handling app.post('/api/orders', async (req, res) => { try { // Validation const { userId, items, paymentMethod } = req.body; if (!userId || !items || !paymentMethod) { return res.status(400).json({ error: 'Missing required fields', required: ['userId', 'items', 'paymentMethod'] }); } if (!Array.isArray(items) || items.length === 0) { return res.status(400).json({ error: 'Items must be a non-empty array' }); } // Create order with error handling let order; try { order = await createOrder({ userId, items, paymentMethod }); } catch (error) { if (error.code === 'INSUFFICIENT_STOCK') { return res.status(409).json({ error: 'Some items are out of stock', unavailable: error.items }); } throw error; // Re-throw for outer handler } // Process payment with retry let payment; try { payment = await retryWithBackoff( () => processPayment(order.id, paymentMethod), { maxRetries: 3 } ); } catch (error) { // Rollback order await cancelOrder(order.id); if (error.code === 'PAYMENT_DECLINED') { return res.status(402).json({ error: 'Payment declined', reason: error.reason }); } throw error; } // Success res.status(201).json({ orderId: order.id, paymentId: payment.id, status: 'confirmed' }); } catch (error) { logger.error('Order creation failed', { body: req.body, error: error.message, stack: error.stack }); res.status(500).json({ error: 'Failed to create order', message: process.env.NODE_ENV === 'development' ? error.message : 'Please try again later' }); } });
User: "Add error handling to database queries"
Agent:
javascriptclass UserRepository { async findById(id) { try { const user = await db.query( 'SELECT * FROM users WHERE id = $1', [id] ); if (!user) { throw new NotFoundError(`User ${id} not found`); } return user; } catch (error) { if (error instanceof NotFoundError) { throw error; } logger.error('Database query failed', { method: 'findById', id, error: error.message }); throw new DatabaseError('Failed to fetch user', { cause: error }); } } async update(id, data) { const client = await db.getClient(); try { await client.query('BEGIN'); const result = await client.query( 'UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING *', [data.name, data.email, id] ); if (result.rowCount === 0) { throw new NotFoundError(`User ${id} not found`); } await client.query('COMMIT'); return result.rows[0]; } catch (error) { await client.query('ROLLBACK'); if (error.code === '23505') { // Unique violation throw new ConflictError('Email already exists'); } if (error instanceof NotFoundError) { throw error; } logger.error('User update failed', { id, data, error: error.message }); throw new DatabaseError('Failed to update user', { cause: error }); } finally { client.release(); } } }
User: "Add resilience to third-party API calls"
Agent:
javascriptclass WeatherAPI { constructor() { this.breaker = new CircuitBreaker(this._fetch.bind(this), { failureThreshold: 5, resetTimeout: 60000 }); this.cache = new Map(); } async getWeather(city) { // Try cache first const cached = this.cache.get(city); if (cached && Date.now() - cached.timestamp < 300000) { // 5 min cache logger.info('Returning cached weather', { city }); return cached.data; } try { // Call with circuit breaker const data = await this.breaker.execute(city); // Update cache this.cache.set(city, { data, timestamp: Date.now() }); return data; } catch (error) { logger.error('Weather API failed', { city, error: error.message }); // Return stale cache if available if (cached) { logger.warn('Returning stale cached data', { city }); return { ...cached.data, _stale: true }; } // Return default return { city, temperature: null, condition: 'Unknown', _error: error.message }; } } async _fetch(city) { const response = await retryWithBackoff( () => fetch(`https://api.weather.com/v1/${city}`), { maxRetries: 3, shouldRetry: (error) => { // Don't retry client errors return !error.statusCode || error.statusCode >= 500; } } ); if (!response.ok) { throw new Error(`Weather API error: ${response.status}`); } return response.json(); } }
Create .errorhandlerrc.json:
json{ "coverage": { "minimum": 80, "target": 95, "failOnBelow": true }, "patterns": { "enableRetry": true, "enableCircuitBreaker": true, "enableFallback": true, "maxRetries": 3, "retryDelay": 1000 }, "logging": { "logLevel": "error", "includeStack": true, "structuredLogging": true }, "customErrors": { "baseClass": "AppError", "errorTypes": [ "ValidationError", "NotFoundError", "UnauthorizedError", "ForbiddenError" ] } }
javascript// Base error class class AppError extends Error { constructor(message, options = {}) { super(message); this.name = this.constructor.name; this.statusCode = options.statusCode || 500; this.code = options.code; this.originalError = options.cause; } } // Domain-specific errors class ValidationError extends AppError { constructor(message, fields) { super(message, { statusCode: 400 }); this.fields = fields; } } class NotFoundError extends AppError { constructor(resource) { super(`${resource} not found`, { statusCode: 404 }); this.resource = resource; } } class UnauthorizedError extends AppError { constructor(message = 'Unauthorized') { super(message, { statusCode: 401 }); } } class ConflictError extends AppError { constructor(message) { super(message, { statusCode: 409 }); } } class ServiceUnavailableError extends AppError { constructor(service) { super(`${service} is temporarily unavailable`, { statusCode: 503 }); this.service = service; } }
MIT License - see LICENSE for details.
Built with 🌸 by 翠花 (Cuihua) for the OpenClaw community.
Because production systems deserve bulletproof error handling.
Made with 🌸 | Cuihua Series | ClawHub Pioneer
_Transform fragile code into resilient systems._
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 9,014 | 14,595 | +62% | 1 | 1 | 0% | 1,907 | 7,951 | +317% | 0 | 0 | — |
case-02 | pass→pass | 7,526 | 5,347 | -29% | 1 | 1 | 0% | 1,710 | 6,081 | +256% | 0 | 0 | — |
case-03 | pass→pass | 10,138 | 7,767 | -23% | 1 | 1 | 0% | 2,252 | 6,653 | +195% | 0 | 0 | — |
case-04 | pass→pass | 5,171 | 4,949 | -4% | 1 | 1 | 0% | 1,274 | 6,096 | +378% | 0 | 0 | — |
case-05 | fail→pass | 4,189 | 3,332 | -20% | 1 | 1 | 0% | 1,081 | 5,639 | +422% | 0 | 0 | — |
case-06 | pass→pass | 10,905 | 13,160 | +21% | 1 | 1 | 0% | 2,435 | 8,065 | +231% | 0 | 0 | — |
case-12 | pass→pass | 9,006 | 10,394 | +15% | 1 | 1 | 0% | 2,090 | 7,371 | +253% | 0 | 0 | — |
case-07 | pass→pass | 15,231 | 22,504 | +48% | 1 | 1 | 0% | 3,534 | 10,300 | +191% | 0 | 0 | — |
case-08 | pass→pass | 12,174 | 9,215 | -24% | 1 | 1 | 0% | 2,400 | 6,895 | +187% | 0 | 0 | — |
case-09 | fail→pass | 6,799 | 7,144 | +5% | 1 | 1 | 0% | 1,402 | 6,803 | +385% | 0 | 0 | — |
case-10 | pass→pass | 11,778 | 9,706 | -18% | 1 | 1 | 0% | 2,561 | 7,175 | +180% | 0 | 0 | — |
case-11 | pass→pass | 9,057 | 10,198 | +13% | 1 | 1 | 0% | 2,147 | 7,719 | +260% | 0 | 0 | — |
case-13 | pass→pass | 11,993 | 13,729 | +14% | 1 | 1 | 0% | 3,062 | 9,003 | +194% | 0 | 0 | — |
case-14 | pass→pass | 10,720 | 7,681 | -28% | 1 | 1 | 0% | 2,332 | 6,814 | +192% | 0 | 0 | — |
case-15 | fail→pass | 7,808 | 3,867 | -50% | 1 | 1 | 0% | 1,762 | 5,651 | +221% | 0 | 0 | — |
case-16 | pass→fail | 14,206 | 11,994 | -16% | 1 | 1 | 0% | 3,720 | 8,058 | +117% | 0 | 0 | — |
case-17 | fail→fail | 12,733 | 14,188 | +11% | 1 | 1 | 0% | 2,874 | 7,868 | +174% | 0 | 0 | — |
case-18 | pass→pass | 14,274 | 14,136 | -1% | 1 | 1 | 0% | 2,644 | 7,766 | +194% | 0 | 0 | — |
case-19 | pass→pass | 11,034 | 3,243 | -71% | 1 | 1 | 0% | 1,868 | 5,500 | +194% | 0 | 0 | — |
case-20 | fail→pass | 11,249 | 10,709 | -5% | 1 | 1 | 0% | 2,366 | 7,367 | +211% | 0 | 0 | — |
case-21 | pass→pass | 7,901 | 4,930 | -38% | 1 | 1 | 0% | 1,558 | 5,885 | +278% | 0 | 0 | — |
case-22 | pass→pass | 10,209 | 6,972 | -32% | 1 | 1 | 0% | 2,156 | 6,380 | +196% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.