feat(error-handling): add network error differentiation, retry, and REST API detection (C.5)
WordPress client improvements: - Add ConnectionError exception for network-level failures - Differentiate DNS, SSL, timeout, and connection refused errors - Add retry with exponential backoff for transient errors (502, 503, 504, 429) - Auth/config errors are never retried - Add request timeout (30s default) Health check improvements: - Detect REST API disabled (403/404 on /wp-json) - Classify errors by type: dns_failure, ssl_error, timeout, connection_refused Bare except cleanup: - Replaced all bare except: with except Exception: across 12 files Tests: 303 passed (13 new tests for error handling) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -553,6 +553,7 @@ class HealthMonitor:
|
||||
|
||||
async def _basic_http_health_check(self, url: str | None, project_id: str) -> dict[str, Any]:
|
||||
"""Basic HTTP health check as a last-resort fallback."""
|
||||
|
||||
import aiohttp
|
||||
|
||||
if not url:
|
||||
@@ -568,8 +569,27 @@ class HealthMonitor:
|
||||
"status_code": resp.status,
|
||||
"message": f"HTTP {resp.status} from {url}",
|
||||
}
|
||||
except TimeoutError:
|
||||
return {
|
||||
"healthy": False,
|
||||
"error_type": "timeout",
|
||||
"message": f"Site at {url} did not respond within 10 seconds.",
|
||||
}
|
||||
except aiohttp.ClientConnectorDNSError:
|
||||
host = url.split("://")[-1].split("/")[0]
|
||||
return {
|
||||
"healthy": False,
|
||||
"error_type": "dns_failure",
|
||||
"message": f"DNS resolution failed for '{host}'. Check the URL.",
|
||||
}
|
||||
except aiohttp.ClientConnectorError:
|
||||
return {
|
||||
"healthy": False,
|
||||
"error_type": "connection_refused",
|
||||
"message": f"Cannot connect to {url}. Server unreachable.",
|
||||
}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "message": f"Connection failed: {e}"}
|
||||
return {"healthy": False, "error_type": "unknown", "message": f"Connection failed: {e}"}
|
||||
|
||||
async def check_all_projects_health(self, include_metrics: bool = True) -> dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -241,8 +241,8 @@ class TokenManager:
|
||||
try:
|
||||
old_payload = self.validate_access_token(token_data.access_token)
|
||||
scope = old_payload.get("scope", "read")
|
||||
except:
|
||||
pass # Old token might be expired, use default
|
||||
except Exception:
|
||||
pass # Old token might be expired, use default scope
|
||||
|
||||
# Generate new tokens
|
||||
new_access_token = self.generate_access_token(
|
||||
|
||||
@@ -448,12 +448,15 @@ class ToolGenerator:
|
||||
|
||||
except Exception as e:
|
||||
# Import custom exceptions for better error handling
|
||||
from plugins.wordpress.client import AuthenticationError, ConfigurationError
|
||||
from plugins.wordpress.client import (
|
||||
AuthenticationError,
|
||||
ConfigurationError,
|
||||
ConnectionError,
|
||||
)
|
||||
|
||||
error_type = type(e).__name__
|
||||
|
||||
if isinstance(e, ConfigurationError):
|
||||
# Configuration error - likely missing env vars
|
||||
logger.error(f"Configuration error in {plugin_type}_{method_name}: {e}")
|
||||
return (
|
||||
f"Configuration Error: {str(e)}\n\n"
|
||||
@@ -464,12 +467,14 @@ class ToolGenerator:
|
||||
)
|
||||
|
||||
elif isinstance(e, AuthenticationError):
|
||||
# Authentication error - 401/403
|
||||
logger.warning(f"Authentication error in {plugin_type}_{method_name}: {e}")
|
||||
return f"Authentication Error: {str(e)}"
|
||||
|
||||
elif isinstance(e, ConnectionError):
|
||||
logger.warning(f"Connection error in {plugin_type}_{method_name}: {e}")
|
||||
return f"Connection Error: {str(e)}"
|
||||
|
||||
else:
|
||||
# Unexpected error
|
||||
logger.error(
|
||||
f"Error in unified handler for {plugin_type}_{method_name}: {e}",
|
||||
exc_info=True,
|
||||
|
||||
Reference in New Issue
Block a user