# Task 4: Error Handling Standardization
## Overview
**Priority**: Low
**Goal**: Standardize error handling across the application
**Target**: Consistent error experience with unified exception hierarchy
**Status**: ✅ COMPLETED
## Completed Tasks
- [x] Create errors directory structure
- [x] Implement custom exception hierarchy in exceptions.py
- [x] Create error handling decorators in handlers.py
- [x] Implement standardized response formats in responses.py
- [x] Update MCP tools to use new error handling
- [x] Update services to use custom exceptions
- [x] Add comprehensive error logging
- [x] Add error recovery mechanisms
- [x] Write tests for error handling
## Current Problem
Error handling is currently inconsistent across the codebase:
- Different error response formats across MCP tools
- Mixed exception types and handling patterns
- No centralized error logging or monitoring
- Inconsistent error messages and user feedback
- No standardized error recovery mechanisms
This makes the code:
- Difficult to debug and troubleshoot
- Inconsistent user experience
- Hard to implement comprehensive error monitoring
- Challenging to provide helpful error recovery suggestions
## Target Structure
```
src/commit_helper_mcp/errors/
├── __init__.py
├── exceptions.py # Custom exception hierarchy
├── handlers.py # Error handling decorators/utilities
└── responses.py # Standardized error response formats
```
## Implementation Steps
### Step 1: Create Error Handling Package
**File**: `src/commit_helper_mcp/errors/__init__.py`
```python
"""
Error Handling for Commit Helper MCP
This package provides standardized error handling with custom exceptions,
consistent response formats, and comprehensive error recovery mechanisms.
"""
from .exceptions import (
CommitHelperMCPError,
GitOperationError,
ValidationError,
ConfigurationError,
RepositoryError,
PluginError,
ServiceError
)
from .handlers import (
handle_errors,
handle_git_errors,
handle_validation_errors,
handle_configuration_errors,
log_error_context
)
from .responses import (
ErrorResponse,
create_error_response,
create_success_response,
format_validation_error,
format_git_error
)
__all__ = [
# Exceptions
'CommitHelperMCPError',
'GitOperationError',
'ValidationError',
'ConfigurationError',
'RepositoryError',
'PluginError',
'ServiceError',
# Handlers
'handle_errors',
'handle_git_errors',
'handle_validation_errors',
'handle_configuration_errors',
'log_error_context',
# Responses
'ErrorResponse',
'create_error_response',
'create_success_response',
'format_validation_error',
'format_git_error'
]
```
### Step 2: Create Custom Exception Hierarchy
**File**: `src/commit_helper_mcp/errors/exceptions.py`
**Responsibilities**:
- Define custom exception hierarchy
- Provide context-rich error information
- Enable specific error handling patterns
- Support error recovery suggestions
```python
"""
Custom Exception Hierarchy
Defines a comprehensive exception hierarchy for the Commitizen MCP Connector
with context-rich error information and recovery suggestions.
"""
from typing import Dict, Any, Optional, List
from dataclasses import dataclass, field
@dataclass
class ErrorContext:
"""Rich error context information."""
operation: str
component: str
details: Dict[str, Any] = field(default_factory=dict)
suggestions: List[str] = field(default_factory=list)
recovery_actions: List[str] = field(default_factory=list)
user_message: Optional[str] = None
class CommitHelperMCPError(Exception):
"""Base exception for all Commit Helper MCP errors."""
def __init__(
self,
message: str,
context: Optional[ErrorContext] = None,
cause: Optional[Exception] = None
):
super().__init__(message)
self.context = context or ErrorContext(operation="unknown", component="unknown")
self.cause = cause
def to_dict(self) -> Dict[str, Any]:
"""Convert exception to dictionary for serialization."""
return {
"error_type": self.__class__.__name__,
"message": str(self),
"context": {
"operation": self.context.operation,
"component": self.context.component,
"details": self.context.details,
"suggestions": self.context.suggestions,
"recovery_actions": self.context.recovery_actions,
"user_message": self.context.user_message
},
"cause": str(self.cause) if self.cause else None
}
def get_user_message(self) -> str:
"""Get user-friendly error message."""
return self.context.user_message or str(self)
def get_suggestions(self) -> List[str]:
"""Get error recovery suggestions."""
return self.context.suggestions
def get_recovery_actions(self) -> List[str]:
"""Get specific recovery actions."""
return self.context.recovery_actions
class GitOperationError(CommitHelperMCPError):
"""Errors related to git operations."""
def __init__(
self,
message: str,
git_command: Optional[str] = None,
repo_path: Optional[str] = None,
cause: Optional[Exception] = None
):
context = ErrorContext(
operation="git_operation",
component="git_service",
details={
"git_command": git_command,
"repo_path": repo_path
},
suggestions=[
"Check if the repository exists and is accessible",
"Verify git is installed and configured",
"Ensure you have proper permissions for the repository"
]
)
super().__init__(message, context, cause)
class ValidationError(CommitHelperMCPError):
"""Errors related to validation (commit messages, inputs, etc.)."""
def __init__(
self,
message: str,
validation_type: Optional[str] = None,
invalid_value: Optional[str] = None,
expected_format: Optional[str] = None,
cause: Optional[Exception] = None
):
context = ErrorContext(
operation="validation",
component="validation_service",
details={
"validation_type": validation_type,
"invalid_value": invalid_value,
"expected_format": expected_format
},
suggestions=[
"Check the format requirements",
"Review the validation rules",
"Use the get_commit_types tool to see valid options"
]
)
super().__init__(message, context, cause)
class ConfigurationError(CommitHelperMCPError):
"""Errors related to configuration loading and validation."""
def __init__(
self,
message: str,
config_file: Optional[str] = None,
config_key: Optional[str] = None,
cause: Optional[Exception] = None
):
context = ErrorContext(
operation="configuration",
component="config_service",
details={
"config_file": config_file,
"config_key": config_key
},
suggestions=[
"Check if configuration files exist and are readable",
"Verify configuration syntax is correct",
"Review environment variable settings"
]
)
super().__init__(message, context, cause)
class RepositoryError(CommitHelperMCPError):
"""Errors related to repository access and validation."""
def __init__(
self,
message: str,
repo_path: Optional[str] = None,
cause: Optional[Exception] = None
):
context = ErrorContext(
operation="repository_access",
component="repository_manager",
details={
"repo_path": repo_path
},
suggestions=[
"Verify the repository path exists",
"Check if it's a valid git repository",
"Ensure proper file permissions"
]
)
super().__init__(message, context, cause)
class PluginError(CommitHelperMCPError):
"""Errors related to Commitizen plugin operations."""
def __init__(
self,
message: str,
plugin_name: Optional[str] = None,
cause: Optional[Exception] = None
):
context = ErrorContext(
operation="plugin_operation",
component="plugin_service",
details={
"plugin_name": plugin_name
},
suggestions=[
"Check if the plugin is installed",
"Verify plugin configuration is correct",
"Try refreshing the configuration"
]
)
super().__init__(message, context, cause)
class ServiceError(CommitHelperMCPError):
"""Errors related to service initialization and operation."""
def __init__(
self,
message: str,
service_name: Optional[str] = None,
cause: Optional[Exception] = None
):
context = ErrorContext(
operation="service_operation",
component=service_name or "unknown_service",
details={
"service_name": service_name
},
suggestions=[
"Check service dependencies are available",
"Verify service configuration",
"Try restarting the service"
]
)
super().__init__(message, context, cause)
# Convenience functions for creating specific errors
def create_git_error(
message: str,
git_command: str = None,
repo_path: str = None,
cause: Exception = None
) -> GitOperationError:
"""Create a GitOperationError with context."""
return GitOperationError(message, git_command, repo_path, cause)
def create_validation_error(
message: str,
validation_type: str = None,
invalid_value: str = None,
expected_format: str = None,
cause: Exception = None
) -> ValidationError:
"""Create a ValidationError with context."""
return ValidationError(message, validation_type, invalid_value, expected_format, cause)
def create_config_error(
message: str,
config_file: str = None,
config_key: str = None,
cause: Exception = None
) -> ConfigurationError:
"""Create a ConfigurationError with context."""
return ConfigurationError(message, config_file, config_key, cause)
```
### Step 3: Create Error Handlers
**File**: `src/commit_helper_mcp/errors/handlers.py`
**Responsibilities**:
- Provide error handling decorators
- Centralize error logging
- Implement error recovery mechanisms
- Convert exceptions to appropriate responses
```python
"""
Error Handlers
Provides decorators and utilities for consistent error handling across
the application with logging, monitoring, and recovery mechanisms.
"""
import logging
import functools
from typing import Dict, Any, Callable, Optional, Type
from .exceptions import (
CommitHelperMCPError,
GitOperationError,
ValidationError,
ConfigurationError,
RepositoryError,
PluginError,
ServiceError
)
from .responses import create_error_response
logger = logging.getLogger(__name__)
def handle_errors(
default_response: Optional[Dict[str, Any]] = None,
log_errors: bool = True,
reraise: bool = False
):
"""
Decorator for comprehensive error handling.
Args:
default_response: Default response to return on error
log_errors: Whether to log errors
reraise: Whether to reraise exceptions after handling
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
try:
return func(*args, **kwargs)
except CommitHelperMCPError as e:
if log_errors:
log_error_context(e, func.__name__)
response = create_error_response(e)
if reraise:
raise
return response
except Exception as e:
# Convert unexpected exceptions to CommitHelperMCPError
mcp_error = ServiceError(
f"Unexpected error in {func.__name__}: {str(e)}",
service_name=func.__module__,
cause=e
)
if log_errors:
log_error_context(mcp_error, func.__name__)
response = create_error_response(mcp_error)
if reraise:
raise mcp_error from e
return response or default_response or {
"error": "An unexpected error occurred",
"success": False
}
return wrapper
return decorator
def handle_git_errors(func: Callable) -> Callable:
"""Decorator specifically for git operation error handling."""
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
try:
return func(*args, **kwargs)
except GitOperationError:
# Already a git error, re-raise
raise
except Exception as e:
# Convert to GitOperationError
git_error = GitOperationError(
f"Git operation failed in {func.__name__}: {str(e)}",
cause=e
)
raise git_error from e
return wrapper
def handle_validation_errors(func: Callable) -> Callable:
"""Decorator specifically for validation error handling."""
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
try:
return func(*args, **kwargs)
except ValidationError:
# Already a validation error, re-raise
raise
except Exception as e:
# Convert to ValidationError
validation_error = ValidationError(
f"Validation failed in {func.__name__}: {str(e)}",
cause=e
)
raise validation_error from e
return wrapper
def handle_configuration_errors(func: Callable) -> Callable:
"""Decorator specifically for configuration error handling."""
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
try:
return func(*args, **kwargs)
except ConfigurationError:
# Already a configuration error, re-raise
raise
except Exception as e:
# Convert to ConfigurationError
config_error = ConfigurationError(
f"Configuration error in {func.__name__}: {str(e)}",
cause=e
)
raise config_error from e
return wrapper
def log_error_context(error: CommitHelperMCPError, function_name: str):
"""Log error with full context information."""
logger.error(
f"Error in {function_name}: {error}",
extra={
"error_type": error.__class__.__name__,
"operation": error.context.operation,
"component": error.context.component,
"details": error.context.details,
"function": function_name,
"cause": str(error.cause) if error.cause else None
}
)
def create_error_recovery_response(
error: CommitHelperMCPError,
additional_context: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Create error response with recovery information."""
response = create_error_response(error)
if additional_context:
response.update(additional_context)
# Add recovery information
if error.get_suggestions():
response["suggestions"] = error.get_suggestions()
if error.get_recovery_actions():
response["recovery_actions"] = error.get_recovery_actions()
return response
class ErrorHandler:
"""Context manager for error handling."""
def __init__(
self,
operation: str,
component: str,
log_errors: bool = True,
reraise: bool = True
):
self.operation = operation
self.component = component
self.log_errors = log_errors
self.reraise = reraise
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
return False
if issubclass(exc_type, CommitHelperMCPError):
if self.log_errors:
log_error_context(exc_val, self.operation)
return not self.reraise
# Convert unexpected exceptions
mcp_error = ServiceError(
f"Unexpected error in {self.operation}: {str(exc_val)}",
service_name=self.component,
cause=exc_val
)
if self.log_errors:
log_error_context(mcp_error, self.operation)
if self.reraise:
raise mcp_error from exc_val
return True
```
### Step 4: Create Response Formatters
**File**: `src/commit_helper_mcp/errors/responses.py`
**Responsibilities**:
- Standardize error response formats
- Provide consistent success responses
- Format errors for different contexts
- Support internationalization (future)
```python
"""
Error Response Formatting
Provides standardized response formatting for errors and success cases
with consistent structure and helpful information.
"""
from typing import Dict, Any, Optional, List
from dataclasses import dataclass, field
from .exceptions import CommitHelperMCPError
@dataclass
class ErrorResponse:
"""Standardized error response structure."""
success: bool = field(default=False)
error: str = field(default="")
error_type: str = field(default="")
details: Dict[str, Any] = field(default_factory=dict)
suggestions: List[str] = field(default_factory=list)
recovery_actions: List[str] = field(default_factory=list)
context: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
result = {
"success": self.success,
"error": self.error
}
if self.error_type:
result["error_type"] = self.error_type
if self.details:
result["details"] = self.details
if self.suggestions:
result["suggestions"] = self.suggestions
if self.recovery_actions:
result["recovery_actions"] = self.recovery_actions
if self.context:
result["context"] = self.context
return result
def create_error_response(
error: CommitHelperMCPError,
include_context: bool = True,
include_suggestions: bool = True
) -> Dict[str, Any]:
"""Create standardized error response from exception."""
response = ErrorResponse(
success=False,
error=error.get_user_message(),
error_type=error.__class__.__name__
)
if include_context and error.context:
response.context = {
"operation": error.context.operation,
"component": error.context.component
}
if error.context.details:
response.details = error.context.details
if include_suggestions:
response.suggestions = error.get_suggestions()
response.recovery_actions = error.get_recovery_actions()
return response.to_dict()
def create_success_response(
data: Dict[str, Any],
message: Optional[str] = None
) -> Dict[str, Any]:
"""Create standardized success response."""
response = {
"success": True,
**data
}
if message:
response["message"] = message
return response
def format_validation_error(
message: str,
invalid_value: Optional[str] = None,
expected_format: Optional[str] = None,
validation_rules: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Format validation error with helpful information."""
from .exceptions import ValidationError
error = ValidationError(
message=message,
validation_type="format_validation",
invalid_value=invalid_value,
expected_format=expected_format
)
if validation_rules:
error.context.suggestions.extend(validation_rules)
return create_error_response(error)
def format_git_error(
message: str,
git_command: Optional[str] = None,
repo_path: Optional[str] = None,
exit_code: Optional[int] = None
) -> Dict[str, Any]:
"""Format git operation error with command context."""
from .exceptions import GitOperationError
error = GitOperationError(
message=message,
git_command=git_command,
repo_path=repo_path
)
if exit_code is not None:
error.context.details["exit_code"] = exit_code
return create_error_response(error)
def format_service_error(
message: str,
service_name: str,
operation: Optional[str] = None
) -> Dict[str, Any]:
"""Format service error with service context."""
from .exceptions import ServiceError
error = ServiceError(
message=message,
service_name=service_name
)
if operation:
error.context.operation = operation
return create_error_response(error)
def add_troubleshooting_info(
response: Dict[str, Any],
troubleshooting_steps: List[str]
) -> Dict[str, Any]:
"""Add troubleshooting information to error response."""
response = response.copy()
if "suggestions" not in response:
response["suggestions"] = []
response["suggestions"].extend(troubleshooting_steps)
return response
def add_documentation_links(
response: Dict[str, Any],
doc_links: Dict[str, str]
) -> Dict[str, Any]:
"""Add documentation links to error response."""
response = response.copy()
if "context" not in response:
response["context"] = {}
response["context"]["documentation"] = doc_links
return response
```
## Integration with Existing Code
### Update MCP Tools to Use Error Handling
**Example Integration**:
```python
from ..errors import handle_errors, create_validation_error, create_git_error
@mcp.tool()
@handle_errors(log_errors=True)
def generate_commit_message(
type: str,
subject: str,
# ... other parameters
) -> Dict[str, Any]:
try:
# Existing implementation
pass
except ValidationError:
raise create_validation_error(
"Invalid commit message format",
validation_type="commit_message",
invalid_value=f"{type}: {subject}"
)
```
### Update Services to Use Custom Exceptions
**Example Service Update**:
```python
from ..errors import GitOperationError, ValidationError, handle_git_errors
class GitPythonCore:
@handle_git_errors
def execute_commit(self, message: str, **kwargs) -> Dict[str, Any]:
try:
# Existing implementation
pass
except Exception as e:
raise GitOperationError(
"Failed to execute git commit",
git_command="commit",
repo_path=str(self.repo_path),
cause=e
)
```
## Implementation Guidelines
### Error Handling Principles
1. **Consistent Structure**: All errors follow the same response format
2. **Rich Context**: Provide detailed context for debugging
3. **User-Friendly**: Include helpful suggestions and recovery actions
4. **Logging**: Comprehensive error logging with context
5. **Recovery**: Enable graceful error recovery where possible
### Migration Strategy
1. **Gradual Adoption**: Update tools and services incrementally
2. **Backward Compatibility**: Maintain existing error response formats initially
3. **Testing**: Comprehensive error scenario testing
## Validation Criteria
### Success Metrics
- [x] All errors follow consistent response format
- [x] Error messages are user-friendly and actionable
- [x] Comprehensive error logging is in place
- [x] Error recovery mechanisms work correctly
- [x] Exception hierarchy covers all error scenarios
- [x] Performance impact is minimal
## Testing Plan
### Unit Tests
- Test custom exception creation and serialization
- Verify error handler decorators work correctly
- Test response formatting functions
- Validate error context information
### Integration Tests
- Test error handling across MCP tools
- Verify error propagation through service layers
- Test error recovery mechanisms
- Validate logging output
### Error Scenario Tests
- Test all major error conditions
- Verify appropriate exceptions are raised
- Test error response consistency
- Validate user-friendly error messages
## Dependencies
### Prerequisites
- Task 3 (Configuration Management) completed
- Understanding of exception handling patterns
- Familiarity with decorator patterns
### Affected Files
- All MCP tool files (error handling integration)
- All service files (exception usage)
- Test files (error scenario testing)
## Estimated Effort
**Time Estimate**: 15 minutes
**Complexity**: Medium
**Risk Level**: Low (additive changes, improves reliability)
## Next Steps
After completing this task:
1. Proceed to Task 5: Test Reorganization
2. Update documentation with error handling guidelines
3. Consider adding error monitoring and alerting
4. Update development guidelines for error handling patterns