From 94f5979cc2dcd0a2decffa044c84aff524572022 Mon Sep 17 00:00:00 2001 From: aaronagent <1115117931@qq.com> Date: Fri, 10 Apr 2026 11:42:40 +0800 Subject: [PATCH] fix(approval,mcp): log silent exception handlers, narrow OAuth catches, close server on error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three silent `except Exception` blocks in approval.py (lines 345, 387, 469) return fallback values with zero logging — making it impossible to debug callback failures, allowlist load errors, or config read issues. Add logger.warning/error calls that match the pattern already used by save_permanent_allowlist() and _smart_approve() in the same file. In mcp_oauth.py, narrow the overly-broad `except Exception` in get_tokens() and get_client_info() to the specific exceptions Pydantic's model_validate() can raise (ValueError, TypeError, KeyError), and include the exception message in the warning. Also wrap the _wait_for_callback() polling loop in try/finally so the HTTPServer is always closed — previously an asyncio.CancelledError or any exception in the loop would leak the server socket. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- tools/approval.py | 9 ++++++--- tools/mcp_oauth.py | 23 ++++++++++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tools/approval.py b/tools/approval.py index b49e444a4e..68a53a01c1 100644 --- a/tools/approval.py +++ b/tools/approval.py @@ -342,7 +342,8 @@ def load_permanent_allowlist() -> set: if patterns: load_permanent(patterns) return patterns - except Exception: + except Exception as e: + logger.warning("Failed to load permanent allowlist: %s", e) return set() @@ -384,7 +385,8 @@ def prompt_dangerous_approval(command: str, description: str, try: return approval_callback(command, description, allow_permanent=allow_permanent) - except Exception: + except Exception as e: + logger.error("Approval callback failed: %s", e, exc_info=True) return "deny" os.environ["HERMES_SPINNER_PAUSE"] = "1" @@ -466,7 +468,8 @@ def _get_approval_config() -> dict: from hermes_cli.config import load_config config = load_config() return config.get("approvals", {}) or {} - except Exception: + except Exception as e: + logger.warning("Failed to load approval config: %s", e) return {} diff --git a/tools/mcp_oauth.py b/tools/mcp_oauth.py index c4d7726769..6b0ef12f20 100644 --- a/tools/mcp_oauth.py +++ b/tools/mcp_oauth.py @@ -198,8 +198,8 @@ class HermesTokenStorage: return None try: return OAuthToken.model_validate(data) - except Exception: - logger.warning("Corrupt tokens at %s -- ignoring", self._tokens_path()) + except (ValueError, TypeError, KeyError) as exc: + logger.warning("Corrupt tokens at %s -- ignoring: %s", self._tokens_path(), exc) return None async def set_tokens(self, tokens: "OAuthToken") -> None: @@ -214,8 +214,8 @@ class HermesTokenStorage: return None try: return OAuthClientInformationFull.model_validate(data) - except Exception: - logger.warning("Corrupt client info at %s -- ignoring", self._client_info_path()) + except (ValueError, TypeError, KeyError) as exc: + logger.warning("Corrupt client info at %s -- ignoring: %s", self._client_info_path(), exc) return None async def set_client_info(self, client_info: "OAuthClientInformationFull") -> None: @@ -343,13 +343,14 @@ async def _wait_for_callback() -> tuple[str, str | None]: timeout = 300.0 poll_interval = 0.5 elapsed = 0.0 - while elapsed < timeout: - if result["auth_code"] is not None or result["error"] is not None: - break - await asyncio.sleep(poll_interval) - elapsed += poll_interval - - server.server_close() + try: + while elapsed < timeout: + if result["auth_code"] is not None or result["error"] is not None: + break + await asyncio.sleep(poll_interval) + elapsed += poll_interval + finally: + server.server_close() if result["error"]: raise RuntimeError(f"OAuth authorization failed: {result['error']}")