From 2912d943705058cc55f7f5fc102c99dbb1efcc27 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 7 Jun 2026 06:14:24 -0700 Subject: [PATCH] fix: guard int(os.getenv()) casts against malformed env vars (#40598) A non-numeric value in env vars like HERMES_STREAM_RETRIES, HERMES_KANBAN_SPECIFY_MAX_TOKENS, GOOGLE_CHAT_MAX_BYTES, IRC_PORT, etc. raised ValueError at import/init and crashed startup. Parse them safely, falling back to the default. Unified onto the existing utils.env_int(key, default) helper for core/ hermes_cli/tools modules instead of the original PR's three duplicate local helpers; plugins keep minimal inline guards (no core-utils import). All existing max()/min()/`or extra.get()` wrappers preserved. Co-authored-by: annguyenNous --- agent/chat_completion_helpers.py | 4 ++-- hermes_cli/kanban_specify.py | 4 +++- hermes_cli/runtime_provider.py | 6 +++--- plugins/browser/firecrawl/provider.py | 5 ++++- plugins/platforms/google_chat/adapter.py | 10 ++++++++-- plugins/platforms/irc/adapter.py | 5 ++++- tools/browser_tool.py | 4 ++-- tools/checkpoint_manager.py | 4 +++- 8 files changed, 29 insertions(+), 13 deletions(-) diff --git a/agent/chat_completion_helpers.py b/agent/chat_completion_helpers.py index 257eece57bd..cbbc9139462 100644 --- a/agent/chat_completion_helpers.py +++ b/agent/chat_completion_helpers.py @@ -34,7 +34,7 @@ from agent.message_sanitization import ( _repair_tool_call_arguments, ) from tools.terminal_tool import is_persistent_env -from utils import base_url_host_matches, base_url_hostname +from utils import base_url_host_matches, base_url_hostname, env_int logger = logging.getLogger(__name__) @@ -2058,7 +2058,7 @@ def interruptible_streaming_api_call(agent, api_kwargs: dict, *, on_first_delta= def _call(): import httpx as _httpx - _max_stream_retries = int(os.getenv("HERMES_STREAM_RETRIES", 2)) + _max_stream_retries = env_int("HERMES_STREAM_RETRIES", 2) try: for _stream_attempt in range(_max_stream_retries + 1): diff --git a/hermes_cli/kanban_specify.py b/hermes_cli/kanban_specify.py index 4bfcce61ee9..40812d835f0 100644 --- a/hermes_cli/kanban_specify.py +++ b/hermes_cli/kanban_specify.py @@ -40,9 +40,11 @@ from typing import Optional from hermes_cli import kanban_db as kb +from utils import env_int + HERMES_KANBAN_SPECIFY_MAX_TOKENS = max( 1500, - int(os.getenv("HERMES_KANBAN_SPECIFY_MAX_TOKENS", "6000")), + env_int("HERMES_KANBAN_SPECIFY_MAX_TOKENS", 6000), ) logger = logging.getLogger(__name__) diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index cca80e988ce..b8165978538 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -31,7 +31,7 @@ from hermes_cli.auth import ( ) from hermes_cli.config import get_compatible_custom_providers, load_config from hermes_constants import OPENROUTER_BASE_URL -from utils import base_url_host_matches, base_url_hostname +from utils import base_url_host_matches, base_url_hostname, env_int def _normalize_custom_provider_name(value: str) -> str: @@ -1144,7 +1144,7 @@ def _resolve_explicit_runtime( str(state.get("agent_key") or "").strip() if _agent_key_is_usable( state, - max(60, int(os.getenv("HERMES_NOUS_MIN_KEY_TTL_SECONDS", "1800"))), + max(60, env_int("HERMES_NOUS_MIN_KEY_TTL_SECONDS", 1800)), ) else "" ) @@ -1343,7 +1343,7 @@ def resolve_runtime_provider( # expired, clear pool_api_key so we fall through to # resolve_nous_runtime_credentials() which handles refresh. if provider == "nous" and entry is not None and pool_api_key: - min_ttl = max(60, int(os.getenv("HERMES_NOUS_MIN_KEY_TTL_SECONDS", "1800"))) + min_ttl = max(60, env_int("HERMES_NOUS_MIN_KEY_TTL_SECONDS", 1800)) nous_state = { "agent_key": getattr(entry, "agent_key", None), "agent_key_expires_at": getattr(entry, "agent_key_expires_at", None), diff --git a/plugins/browser/firecrawl/provider.py b/plugins/browser/firecrawl/provider.py index 2c605134a01..50f813f6018 100644 --- a/plugins/browser/firecrawl/provider.py +++ b/plugins/browser/firecrawl/provider.py @@ -78,7 +78,10 @@ class FirecrawlBrowserProvider(BrowserProvider): } def create_session(self, task_id: str) -> Dict[str, object]: - ttl = int(os.environ.get("FIRECRAWL_BROWSER_TTL", "300")) + try: + ttl = int(os.environ.get("FIRECRAWL_BROWSER_TTL", "300")) + except (ValueError, TypeError): + ttl = 300 body: Dict[str, object] = {"ttl": ttl} diff --git a/plugins/platforms/google_chat/adapter.py b/plugins/platforms/google_chat/adapter.py index f91a5441704..6f738488123 100644 --- a/plugins/platforms/google_chat/adapter.py +++ b/plugins/platforms/google_chat/adapter.py @@ -540,8 +540,14 @@ class GoogleChatAdapter(BasePlatformAdapter): # they don't sit in the chat forever as "Hermes is thinking…". self._orphan_typing_messages: Dict[str, List[str]] = {} # FlowControl knobs (env-configurable). - self._max_messages = int(os.getenv("GOOGLE_CHAT_MAX_MESSAGES", "1")) - self._max_bytes = int(os.getenv("GOOGLE_CHAT_MAX_BYTES", str(16 * 1024 * 1024))) + try: + self._max_messages = int(os.getenv("GOOGLE_CHAT_MAX_MESSAGES", "1")) + except (ValueError, TypeError): + self._max_messages = 1 + try: + self._max_bytes = int(os.getenv("GOOGLE_CHAT_MAX_BYTES", str(16 * 1024 * 1024))) + except (ValueError, TypeError): + self._max_bytes = 16 * 1024 * 1024 # ------------------------------------------------------------------ # Configuration loading and validation diff --git a/plugins/platforms/irc/adapter.py b/plugins/platforms/irc/adapter.py index 2d06cffbdeb..804e1dbc041 100644 --- a/plugins/platforms/irc/adapter.py +++ b/plugins/platforms/irc/adapter.py @@ -107,7 +107,10 @@ class IRCAdapter(BasePlatformAdapter): # Connection settings (env vars override config.yaml) self.server = os.getenv("IRC_SERVER") or extra.get("server", "") - self.port = int(os.getenv("IRC_PORT") or extra.get("port", 6697)) + try: + self.port = int(os.getenv("IRC_PORT") or extra.get("port", 6697)) + except (ValueError, TypeError): + self.port = 6697 self.nickname = os.getenv("IRC_NICKNAME") or extra.get("nickname", "hermes-bot") self.channel = os.getenv("IRC_CHANNEL") or extra.get("channel", "") self.use_tls = ( diff --git a/tools/browser_tool.py b/tools/browser_tool.py index 482f4e17845..5a486b7f244 100644 --- a/tools/browser_tool.py +++ b/tools/browser_tool.py @@ -66,7 +66,7 @@ from typing import Dict, Any, Optional, List, Tuple, Union from pathlib import Path from agent.auxiliary_client import call_llm from hermes_constants import get_hermes_home -from utils import is_truthy_value +from utils import env_int, is_truthy_value from hermes_cli.config import cfg_get try: @@ -1178,7 +1178,7 @@ _cleanup_done = False # Session inactivity timeout (seconds) - cleanup if no activity for this long # Default: 5 minutes. Needs headroom for LLM reasoning between browser commands, # especially when subagents are doing multi-step browser tasks. -BROWSER_SESSION_INACTIVITY_TIMEOUT = int(os.environ.get("BROWSER_INACTIVITY_TIMEOUT", "300")) +BROWSER_SESSION_INACTIVITY_TIMEOUT = env_int("BROWSER_INACTIVITY_TIMEOUT", 300) # Track last activity time per session _session_last_activity: Dict[str, float] = {} diff --git a/tools/checkpoint_manager.py b/tools/checkpoint_manager.py index 16ce12fc60e..e4f48f80da1 100644 --- a/tools/checkpoint_manager.py +++ b/tools/checkpoint_manager.py @@ -60,6 +60,8 @@ from pathlib import Path from hermes_constants import get_hermes_home from typing import Dict, List, Optional, Set, Tuple +from utils import env_int + logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- @@ -139,7 +141,7 @@ DEFAULT_EXCLUDES = [ ] # Git subprocess timeout (seconds). -_GIT_TIMEOUT: int = max(10, min(60, int(os.getenv("HERMES_CHECKPOINT_TIMEOUT", "30")))) +_GIT_TIMEOUT: int = max(10, min(60, env_int("HERMES_CHECKPOINT_TIMEOUT", 30))) # Max files to snapshot — skip huge directories to avoid slowdowns. _MAX_FILES = 50_000