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:
airano
2026-02-19 14:58:16 +03:30
parent c57fd666c9
commit 647a650629
12 changed files with 559 additions and 67 deletions

View File

@@ -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]:
"""

View File

@@ -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(

View File

@@ -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,