Claude-skill-registry errors-confirmations
This skill should be used when implementing graceful error handling and user confirmations in both backend and frontend, including friendly tool not found messages, action confirmations, error toasts, and loading indicators.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/errors-confirmations" ~/.claude/skills/majiayu000-claude-skill-registry-errors-confirmations && rm -rf "$T"
manifest:
skills/data/errors-confirmations/SKILL.mdsource content
Errors & Confirmations Skill
This skill provides guidance for implementing graceful error handling and user confirmations.
Purpose
Implement graceful errors in backend/frontend:
- Tool not found → "Task not found, try again"
- Always confirm actions in response (e.g., "Task added!")
- Frontend: Show error toasts, loading indicators
When to Use
Use this skill when:
- Building error handling for chat API
- Creating user feedback mechanisms
- Implementing loading states
- Adding confirmation messages
Capabilities
- Backend Errors: Structured error responses with user-friendly messages
- Frontend Feedback: Toast notifications, loading states
- Action Confirmations: Clear success messages for user actions
- Error Recovery: Graceful degradation and retry suggestions
Backend Error Handling
Error Response Models
from pydantic import BaseModel from typing import Optional, List from enum import Enum class ErrorSeverity(str, Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" class ErrorResponse(BaseModel): success: bool = False error: str message: str # User-friendly message severity: ErrorSeverity = ErrorSeverity.MEDIUM retryable: bool = True suggestion: Optional[str] = None class ToolError(Exception): def __init__( self, message: str, user_message: str, severity: ErrorSeverity = ErrorSeverity.MEDIUM, retryable: bool = True, suggestion: Optional[str] = None ): self.message = message self.user_message = user_message self.severity = severity self.retryable = retryable self.suggestion = suggestion super().__init__(message)
Error Handlers
from fastapi import Request, HTTPException from fastapi.responses import JSONResponse @app.exception_handler(ToolError) async def tool_error_handler(request: Request, exc: ToolError): return JSONResponse( status_code=400 if exc.severity == ErrorSeverity.LOW else 500, content={ "success": False, "error": exc.message, "message": exc.user_message, "severity": exc.severity.value, "retryable": exc.retryable, "suggestion": exc.suggestion } ) @app.exception_handler(HTTPException) async def http_error_handler(request: Request, exc: HTTPException): user_messages = { 400: "There was an issue with your request. Please check and try again.", 401: "Please sign in to continue.", 403: "You don't have permission for this action.", 404: "The requested item was not found.", 500: "Something went wrong on our end. Please try again." } return JSONResponse( status_code=exc.status_code, content={ "success": False, "error": exc.detail, "message": user_messages.get(exc.status_code, "An error occurred"), "retryable": exc.status_code >= 500 } )
Tool-Specific Errors
async def get_task_by_id(user_id: str, task_id: str) -> Task: """Get task with proper error handling.""" result = await session.execute( select(Task).where( Task.id == task_id, Task.user_id == user_id ) ) task = result.scalar_one_or_none() if not task: raise ToolError( message=f"Task {task_id} not found", user_message="I couldn't find that task. It may have been deleted already.", severity=ErrorSeverity.LOW, retryable=False, suggestion="Try listing your tasks to see what's available." ) return task async def add_task(user_id: str, title: str, **kwargs) -> dict: """Add task with confirmation.""" try: task = Task(user_id=user_id, title=title, **kwargs) session.add(task) await session.commit() await session.refresh(task) return { "success": True, "message": f"Task added successfully!", "task": task_to_dict(task) } except Exception as e: raise ToolError( message=f"Failed to add task: {str(e)}", user_message="I wasn't able to add that task. Please try again.", severity=ErrorSeverity.MEDIUM, retryable=True, suggestion="Check that the title isn't too long and try once more." )
Frontend Error Handling
Toast Notifications
// components/Toast.tsx interface ToastProps { message: string; type: "success" | "error" | "info" | "warning"; onClose: () => void; } export function Toast({ message, type, onClose }: ToastProps) { const styles = { success: "bg-green-500", error: "bg-red-500", info: "bg-blue-500", warning: "bg-yellow-500" }; return ( <div className={`toast ${styles[type]} fixed bottom-4 right-4 text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-2 animate-fade-in`}> <span>{type === "success" ? "✓" : type === "error" ? "✕" : "ℹ"}</span> <span>{message}</span> <button onClick={onClose} className="ml-2 hover:opacity-80">×</button> </div> ); }
Toast Container
// contexts/ToastContext.tsx "use client"; import { createContext, useContext, useState, ReactNode } from "react"; import { Toast } from "@/components/Toast"; interface ToastItem { id: string; message: string; type: "success" | "error" | "info" | "warning"; } interface ToastContextType { showToast: (message: string, type: ToastItem["type"]) => void; } const ToastContext = createContext<ToastContextType | null>(null); export function ToastProvider({ children }: { children: ReactNode }) { const [toasts, setToasts] = useState<ToastItem[]>([]); const showToast = (message: string, type: ToastItem["type"]) => { const id = Date.now().toString(); setToasts(prev => [...prev, { id, message, type }]); // Auto-remove after 5 seconds setTimeout(() => { setToasts(prev => prev.filter(t => t.id !== id)); }, 5000); }; return ( <ToastContext.Provider value={{ showToast }}> {children} <div className="fixed bottom-4 right-4 z-50 space-y-2"> {toasts.map(toast => ( <Toast key={toast.id} message={toast.message} type={toast.type} onClose={() => setToasts(prev => prev.filter(t => t.id !== toast.id))} /> ))} </div> </ToastContext.Provider> ); } export function useToast() { const context = useContext(ToastContext); if (!context) { throw new Error("useToast must be used within ToastProvider"); } return context; }
Loading Indicator
// components/LoadingIndicator.tsx export function LoadingIndicator() { return ( <div className="flex items-center gap-2 text-gray-500 p-2"> <div className="typing-dots"> <span></span> <span></span> <span></span> </div> <span className="text-sm">Thinking...</span> </div> ); } export function LoadingButton({ children, loading, disabled, onClick, className = "" }: { children: React.ReactNode; loading: boolean; disabled?: boolean; onClick?: () => void; className?: string; }) { return ( <button onClick={onClick} disabled={loading || disabled} className={`relative ${className}`} > {loading && ( <span className="absolute inset-0 flex items-center justify-center"> <Spinner /> </span> )} <span className={loading ? "invisible" : ""}>{children}</span> </button> ); } function Spinner() { return ( <svg className="animate-spin h-5 w-5" viewBox="0 0 24 24"> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> ); }
Error Boundary
// components/ErrorBoundary.tsx "use client"; import { Component, ReactNode } from "react"; import { useToast } from "@/contexts/ToastContext"; interface ErrorBoundaryProps { children: ReactNode; fallback?: ReactNode; } interface State { hasError: boolean; error?: Error; } export class ErrorBoundary extends Component<ErrorBoundaryProps, State> { state: State = { hasError: false }; static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: any) { console.error("Error caught by boundary:", error, errorInfo); } render() { if (this.state.hasError) { if (this.props.fallback) { return this.props.fallback; } return ( <div className="p-4 bg-red-50 border border-red-200 rounded-lg"> <h3 className="font-bold text-red-800">Something went wrong</h3> <p className="text-red-600 mt-1">Please refresh the page and try again.</p> <button onClick={() => this.setState({ hasError: false })} className="mt-2 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700" > Try Again </button> </div> ); } return this.props.children; } }
Confirmation Messages
# Backend confirmation templates CONFIRMATIONS = { "add_task": "✓ Task added! I've created '{title}' for you.", "complete_task": "✓ Done! '{title}' is marked as complete.", "delete_task": "✓ Removed '{title}' from your tasks.", "update_task": "✓ Updated '{title}' with your changes.", "list_tasks": "Here are your {count} {status} tasks:" } def format_confirmation(action: str, **kwargs) -> str: """Format a confirmation message.""" template = CONFIRMATIONS.get(action, "✓ Action completed!") return template.format(**kwargs)
API Response Wrapper
def success_response( message: str, data: Optional[dict] = None, confirmation: bool = True ) -> dict: """Create a success response with optional confirmation.""" response = { "success": True, "message": message, "confirmation": confirmation } if data: response["data"] = data return response def error_response( user_message: str, error: Optional[str] = None, suggestion: Optional[str] = None ) -> dict: """Create an error response.""" return { "success": False, "message": user_message, "error": error, "suggestion": suggestion }
Verification Checklist
- Tool errors return user-friendly messages
- Backend confirms actions ("Task added!")
- Frontend shows toast notifications
- Loading indicators during API calls
- Error boundary catches React errors
- Suggestions provided for recovery
- Graceful degradation on failures