refactor: enhance error handling with structured logging across multiple modules

- Updated various modules including cli.py, run_agent.py, gateway, and tools to replace silent exception handling with structured logging.
- Improved error messages to provide more context, aiding in debugging and monitoring.
- Ensured consistent logging practices throughout the codebase, enhancing traceability and maintainability.
This commit is contained in:
teknium1 2026-02-21 03:32:11 -08:00
parent cbff1b818c
commit 748fd3db88
14 changed files with 134 additions and 110 deletions

8
cli.py
View file

@ -823,8 +823,8 @@ class HermesCLI:
try: try:
from hermes_state import SessionDB from hermes_state import SessionDB
self._session_db = SessionDB() self._session_db = SessionDB()
except Exception: except Exception as e:
pass # SQLite session store is optional logger.debug("SQLite session store not available: %s", e)
try: try:
self.agent = AIAgent( self.agent = AIAgent(
@ -2130,8 +2130,8 @@ class HermesCLI:
if hasattr(self, '_session_db') and self._session_db and self.agent: if hasattr(self, '_session_db') and self._session_db and self.agent:
try: try:
self._session_db.end_session(self.agent.session_id, "cli_close") self._session_db.end_session(self.agent.session_id, "cli_close")
except Exception: except Exception as e:
pass logger.debug("Could not close session in DB: %s", e)
_run_cleanup() _run_cleanup()
print("\nGoodbye! ⚕") print("\nGoodbye! ⚕")

View file

@ -8,9 +8,12 @@ Uses discord.py library for:
""" """
import asyncio import asyncio
import logging
import os import os
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
logger = logging.getLogger(__name__)
try: try:
import discord import discord
from discord import Message as DiscordMessage, Intents from discord import Message as DiscordMessage, Intents
@ -177,8 +180,8 @@ class DiscordAdapter(BasePlatformAdapter):
try: try:
ref_msg = await channel.fetch_message(int(reply_to)) ref_msg = await channel.fetch_message(int(reply_to))
reference = ref_msg reference = ref_msg
except Exception: except Exception as e:
pass # Ignore if we can't find the referenced message logger.debug("Could not fetch reply-to message: %s", e)
for i, chunk in enumerate(chunks): for i, chunk in enumerate(chunks):
msg = await channel.send( msg = await channel.send(
@ -363,8 +366,8 @@ class DiscordAdapter(BasePlatformAdapter):
# Send a followup to close the interaction if needed # Send a followup to close the interaction if needed
try: try:
await interaction.followup.send("Processing complete~", ephemeral=True) await interaction.followup.send("Processing complete~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="new", description="Start a new conversation") @tree.command(name="new", description="Start a new conversation")
async def slash_new(interaction: discord.Interaction): async def slash_new(interaction: discord.Interaction):
@ -373,8 +376,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("New conversation started~", ephemeral=True) await interaction.followup.send("New conversation started~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="reset", description="Reset your Hermes session") @tree.command(name="reset", description="Reset your Hermes session")
async def slash_reset(interaction: discord.Interaction): async def slash_reset(interaction: discord.Interaction):
@ -383,8 +386,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("Session reset~", ephemeral=True) await interaction.followup.send("Session reset~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="model", description="Show or change the model") @tree.command(name="model", description="Show or change the model")
@discord.app_commands.describe(name="Model name (e.g. anthropic/claude-sonnet-4). Leave empty to see current.") @discord.app_commands.describe(name="Model name (e.g. anthropic/claude-sonnet-4). Leave empty to see current.")
@ -394,8 +397,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("Done~", ephemeral=True) await interaction.followup.send("Done~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="personality", description="Set a personality") @tree.command(name="personality", description="Set a personality")
@discord.app_commands.describe(name="Personality name. Leave empty to list available.") @discord.app_commands.describe(name="Personality name. Leave empty to list available.")
@ -405,8 +408,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("Done~", ephemeral=True) await interaction.followup.send("Done~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="retry", description="Retry your last message") @tree.command(name="retry", description="Retry your last message")
async def slash_retry(interaction: discord.Interaction): async def slash_retry(interaction: discord.Interaction):
@ -415,8 +418,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("Retrying~", ephemeral=True) await interaction.followup.send("Retrying~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="undo", description="Remove the last exchange") @tree.command(name="undo", description="Remove the last exchange")
async def slash_undo(interaction: discord.Interaction): async def slash_undo(interaction: discord.Interaction):
@ -425,8 +428,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("Done~", ephemeral=True) await interaction.followup.send("Done~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="status", description="Show Hermes session status") @tree.command(name="status", description="Show Hermes session status")
async def slash_status(interaction: discord.Interaction): async def slash_status(interaction: discord.Interaction):
@ -435,8 +438,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("Status sent~", ephemeral=True) await interaction.followup.send("Status sent~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
@tree.command(name="stop", description="Stop the running Hermes agent") @tree.command(name="stop", description="Stop the running Hermes agent")
async def slash_stop(interaction: discord.Interaction): async def slash_stop(interaction: discord.Interaction):
@ -445,8 +448,8 @@ class DiscordAdapter(BasePlatformAdapter):
await self.handle_message(event) await self.handle_message(event)
try: try:
await interaction.followup.send("Stop requested~", ephemeral=True) await interaction.followup.send("Stop requested~", ephemeral=True)
except Exception: except Exception as e:
pass logger.debug("Discord followup failed: %s", e)
def _build_slash_event(self, interaction: discord.Interaction, text: str) -> MessageEvent: def _build_slash_event(self, interaction: discord.Interaction, text: str) -> MessageEvent:
"""Build a MessageEvent from a Discord slash command interaction.""" """Build a MessageEvent from a Discord slash command interaction."""

View file

@ -17,10 +17,13 @@ with different backends via a bridge pattern.
import asyncio import asyncio
import json import json
import logging
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
logger = logging.getLogger(__name__)
import sys import sys
sys.path.insert(0, str(__file__).rsplit("/", 3)[0]) sys.path.insert(0, str(__file__).rsplit("/", 3)[0])
@ -246,8 +249,8 @@ class WhatsAppAdapter(BasePlatformAdapter):
"type": "group" if data.get("isGroup") else "dm", "type": "group" if data.get("isGroup") else "dm",
"participants": data.get("participants", []), "participants": data.get("participants", []),
} }
except Exception: except Exception as e:
pass logger.debug("Could not get WhatsApp chat info for %s: %s", chat_id, e)
return {"name": chat_id, "type": "dm"} return {"name": chat_id, "type": "dm"}

View file

@ -479,8 +479,8 @@ class GatewayRunner:
self._pending_approvals[session_key] = _last_pending_approval.copy() self._pending_approvals[session_key] = _last_pending_approval.copy()
# Clear the global so it doesn't leak to other sessions # Clear the global so it doesn't leak to other sessions
_last_pending_approval.clear() _last_pending_approval.clear()
except Exception: except Exception as e:
pass logger.debug("Failed to check pending approvals: %s", e)
# Save the full conversation to the transcript, including tool calls. # Save the full conversation to the transcript, including tool calls.
# This preserves the complete agent loop (tool_calls, tool results, # This preserves the complete agent loop (tool_calls, tool results,
@ -973,8 +973,8 @@ class GatewayRunner:
with open(config_path, 'r') as f: with open(config_path, 'r') as f:
user_config = yaml.safe_load(f) or {} user_config = yaml.safe_load(f) or {}
platform_toolsets_config = user_config.get("platform_toolsets", {}) platform_toolsets_config = user_config.get("platform_toolsets", {})
except Exception: except Exception as e:
pass logger.debug("Could not load platform_toolsets config: %s", e)
# Map platform enum to config key # Map platform enum to config key
platform_config_key = { platform_config_key = {

View file

@ -8,6 +8,7 @@ Handles:
- Dynamic system prompt injection (agent knows its context) - Dynamic system prompt injection (agent knows its context)
""" """
import logging
import os import os
import json import json
import uuid import uuid
@ -16,6 +17,8 @@ from datetime import datetime, timedelta
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
logger = logging.getLogger(__name__)
from .config import ( from .config import (
Platform, Platform,
GatewayConfig, GatewayConfig,
@ -388,8 +391,8 @@ class SessionStore:
if self._db: if self._db:
try: try:
self._db.end_session(entry.session_id, "session_reset") self._db.end_session(entry.session_id, "session_reset")
except Exception: except Exception as e:
pass logger.debug("Session DB operation failed: %s", e)
# Create new session # Create new session
session_id = f"{now.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}" session_id = f"{now.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
@ -443,8 +446,8 @@ class SessionStore:
self._db.update_token_counts( self._db.update_token_counts(
entry.session_id, input_tokens, output_tokens entry.session_id, input_tokens, output_tokens
) )
except Exception: except Exception as e:
pass logger.debug("Session DB operation failed: %s", e)
def reset_session(self, session_key: str) -> Optional[SessionEntry]: def reset_session(self, session_key: str) -> Optional[SessionEntry]:
"""Force reset a session, creating a new session ID.""" """Force reset a session, creating a new session ID."""
@ -459,8 +462,8 @@ class SessionStore:
if self._db: if self._db:
try: try:
self._db.end_session(old_entry.session_id, "session_reset") self._db.end_session(old_entry.session_id, "session_reset")
except Exception: except Exception as e:
pass logger.debug("Session DB operation failed: %s", e)
now = datetime.now() now = datetime.now()
session_id = f"{now.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}" session_id = f"{now.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
@ -487,8 +490,8 @@ class SessionStore:
source=old_entry.platform.value if old_entry.platform else "unknown", source=old_entry.platform.value if old_entry.platform else "unknown",
user_id=old_entry.origin.user_id if old_entry.origin else None, user_id=old_entry.origin.user_id if old_entry.origin else None,
) )
except Exception: except Exception as e:
pass logger.debug("Session DB operation failed: %s", e)
return new_entry return new_entry
@ -523,8 +526,8 @@ class SessionStore:
tool_calls=message.get("tool_calls"), tool_calls=message.get("tool_calls"),
tool_call_id=message.get("tool_call_id"), tool_call_id=message.get("tool_call_id"),
) )
except Exception: except Exception as e:
pass logger.debug("Session DB operation failed: %s", e)
# Also write legacy JSONL (keeps existing tooling working during transition) # Also write legacy JSONL (keeps existing tooling working during transition)
transcript_path = self.get_transcript_path(session_id) transcript_path = self.get_transcript_path(session_id)
@ -539,8 +542,8 @@ class SessionStore:
messages = self._db.get_messages_as_conversation(session_id) messages = self._db.get_messages_as_conversation(session_id)
if messages: if messages:
return messages return messages
except Exception: except Exception as e:
pass logger.debug("Could not load messages from DB: %s", e)
# Fall back to legacy JSONL # Fall back to legacy JSONL
transcript_path = self.get_transcript_path(session_id) transcript_path = self.get_transcript_path(session_id)

View file

@ -16,6 +16,7 @@ Architecture:
from __future__ import annotations from __future__ import annotations
import json import json
import logging
import os import os
import stat import stat
import time import time
@ -32,6 +33,8 @@ import yaml
from hermes_cli.config import get_hermes_home, get_config_path from hermes_cli.config import get_hermes_home, get_config_path
from hermes_constants import OPENROUTER_BASE_URL from hermes_constants import OPENROUTER_BASE_URL
logger = logging.getLogger(__name__)
try: try:
import fcntl import fcntl
except Exception: except Exception:
@ -314,8 +317,8 @@ def resolve_provider(
state = _load_provider_state(auth_store, active) state = _load_provider_state(auth_store, active)
if state and (state.get("access_token") or state.get("refresh_token")): if state and (state.get("access_token") or state.get("refresh_token")):
return active return active
except Exception: except Exception as e:
pass logger.debug("Could not detect active auth provider: %s", e)
if os.getenv("OPENAI_API_KEY") or os.getenv("OPENROUTER_API_KEY"): if os.getenv("OPENAI_API_KEY") or os.getenv("OPENROUTER_API_KEY"):
return "openrouter" return "openrouter"
@ -578,8 +581,8 @@ def fetch_nous_models(
try: try:
err = response.json() err = response.json()
description = str(err.get("error_description") or err.get("error") or description) description = str(err.get("error_description") or err.get("error") or description)
except Exception: except Exception as e:
pass logger.debug("Could not parse error response JSON: %s", e)
raise AuthError(description, provider="nous", code="models_fetch_failed") raise AuthError(description, provider="nous", code="models_fetch_failed")
payload = response.json() payload = response.json()

View file

@ -39,9 +39,13 @@ env_path = PROJECT_ROOT / '.env'
if env_path.exists(): if env_path.exists():
load_dotenv(dotenv_path=env_path) load_dotenv(dotenv_path=env_path)
import logging
from hermes_cli import __version__ from hermes_cli import __version__
from hermes_constants import OPENROUTER_BASE_URL from hermes_constants import OPENROUTER_BASE_URL
logger = logging.getLogger(__name__)
def cmd_chat(args): def cmd_chat(args):
"""Run interactive chat CLI.""" """Run interactive chat CLI."""
@ -512,8 +516,8 @@ def cmd_update(args):
print(f" + {len(result['copied'])} new skill(s): {', '.join(result['copied'])}") print(f" + {len(result['copied'])} new skill(s): {', '.join(result['copied'])}")
else: else:
print(" ✓ Skills are up to date") print(" ✓ Skills are up to date")
except Exception: except Exception as e:
pass logger.debug("Skills sync during update failed: %s", e)
# Check for config migrations # Check for config migrations
print() print()

View file

@ -12,11 +12,14 @@ Guides users through:
Config files are stored in ~/.hermes/ for easy access. Config files are stored in ~/.hermes/ for easy access.
""" """
import logging
import os import os
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
logger = logging.getLogger(__name__)
PROJECT_ROOT = Path(__file__).parent.parent.resolve() PROJECT_ROOT = Path(__file__).parent.parent.resolve()
# Import config helpers # Import config helpers
@ -488,8 +491,8 @@ def run_setup_wizard(args):
inference_base_url=creds.get("base_url", ""), inference_base_url=creds.get("base_url", ""),
api_key=creds.get("api_key", ""), api_key=creds.get("api_key", ""),
) )
except Exception: except Exception as e:
pass logger.debug("Could not fetch Nous models after login: %s", e)
except SystemExit: except SystemExit:
print_warning("Nous Portal login was cancelled or failed.") print_warning("Nous Portal login was cancelled or failed.")

View file

@ -636,8 +636,8 @@ def build_skills_system_prompt() -> str:
match = re.search(r"^---\s*\n.*?description:\s*(.+?)\s*\n.*?^---", content, re.MULTILINE | re.DOTALL) match = re.search(r"^---\s*\n.*?description:\s*(.+?)\s*\n.*?^---", content, re.MULTILINE | re.DOTALL)
if match: if match:
category_descriptions[category] = match.group(1).strip() category_descriptions[category] = match.group(1).strip()
except Exception: except Exception as e:
pass logger.debug("Could not read skill description %s: %s", desc_file, e)
index_lines = [] index_lines = []
for category in sorted(skills_by_category.keys()): for category in sorted(skills_by_category.keys()):
@ -748,8 +748,8 @@ def build_context_files_prompt(cwd: str = None) -> str:
if content: if content:
rel_path = agents_path.relative_to(cwd_path) rel_path = agents_path.relative_to(cwd_path)
total_agents_content += f"## {rel_path}\n\n{content}\n\n" total_agents_content += f"## {rel_path}\n\n{content}\n\n"
except Exception: except Exception as e:
pass logger.debug("Could not read %s: %s", agents_path, e)
if total_agents_content: if total_agents_content:
total_agents_content = _truncate_content(total_agents_content, "AGENTS.md") total_agents_content = _truncate_content(total_agents_content, "AGENTS.md")
@ -765,8 +765,8 @@ def build_context_files_prompt(cwd: str = None) -> str:
content = cursorrules_file.read_text(encoding="utf-8").strip() content = cursorrules_file.read_text(encoding="utf-8").strip()
if content: if content:
cursorrules_content += f"## .cursorrules\n\n{content}\n\n" cursorrules_content += f"## .cursorrules\n\n{content}\n\n"
except Exception: except Exception as e:
pass logger.debug("Could not read .cursorrules: %s", e)
# Check for .cursor/rules/*.mdc files # Check for .cursor/rules/*.mdc files
cursor_rules_dir = cwd_path / ".cursor" / "rules" cursor_rules_dir = cwd_path / ".cursor" / "rules"
@ -777,8 +777,8 @@ def build_context_files_prompt(cwd: str = None) -> str:
content = mdc_file.read_text(encoding="utf-8").strip() content = mdc_file.read_text(encoding="utf-8").strip()
if content: if content:
cursorrules_content += f"## .cursor/rules/{mdc_file.name}\n\n{content}\n\n" cursorrules_content += f"## .cursor/rules/{mdc_file.name}\n\n{content}\n\n"
except Exception: except Exception as e:
pass logger.debug("Could not read %s: %s", mdc_file, e)
if cursorrules_content: if cursorrules_content:
cursorrules_content = _truncate_content(cursorrules_content, ".cursorrules") cursorrules_content = _truncate_content(cursorrules_content, ".cursorrules")
@ -807,8 +807,8 @@ def build_context_files_prompt(cwd: str = None) -> str:
content = _truncate_content(content, "SOUL.md") content = _truncate_content(content, "SOUL.md")
soul_content = f"## SOUL.md\n\nIf SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.\n\n{content}" soul_content = f"## SOUL.md\n\nIf SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.\n\n{content}"
sections.append(soul_content) sections.append(soul_content)
except Exception: except Exception as e:
pass logger.debug("Could not read SOUL.md from %s: %s", soul_path, e)
# ----- Assemble ----- # ----- Assemble -----
if not sections: if not sections:
@ -1320,8 +1320,8 @@ class AIAgent:
}, },
user_id=None, user_id=None,
) )
except Exception: except Exception as e:
pass logger.debug("Session DB create_session failed: %s", e)
# In-memory todo list for task planning (one per agent/session) # In-memory todo list for task planning (one per agent/session)
from tools.todo_tool import TodoStore from tools.todo_tool import TodoStore
@ -1730,8 +1730,8 @@ class AIAgent:
tool_call_id=msg.get("tool_call_id"), tool_call_id=msg.get("tool_call_id"),
finish_reason=msg.get("finish_reason"), finish_reason=msg.get("finish_reason"),
) )
except Exception: except Exception as e:
pass logger.debug("Session DB append_message failed: %s", e)
def _get_messages_up_to_last_assistant(self, messages: List[Dict]) -> List[Dict]: def _get_messages_up_to_last_assistant(self, messages: List[Dict]) -> List[Dict]:
""" """
@ -2048,8 +2048,8 @@ class AIAgent:
api_key = None api_key = None
try: try:
api_key = getattr(self.client, "api_key", None) api_key = getattr(self.client, "api_key", None)
except Exception: except Exception as e:
pass logger.debug("Could not extract API key for debug dump: %s", e)
dump_payload: Dict[str, Any] = { dump_payload: Dict[str, Any] = {
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
@ -2085,8 +2085,8 @@ class AIAgent:
try: try:
error_info["response_status"] = getattr(response_obj, "status_code", None) error_info["response_status"] = getattr(response_obj, "status_code", None)
error_info["response_text"] = response_obj.text error_info["response_text"] = response_obj.text
except Exception: except Exception as e:
pass logger.debug("Could not extract error response details: %s", e)
dump_payload["error"] = error_info dump_payload["error"] = error_info
@ -2174,8 +2174,8 @@ class AIAgent:
for child in self._active_children: for child in self._active_children:
try: try:
child.interrupt(message) child.interrupt(message)
except Exception: except Exception as e:
pass logger.debug("Failed to propagate interrupt to child agent: %s", e)
if not self.quiet_mode: if not self.quiet_mode:
print(f"\n⚡ Interrupt requested" + (f": '{message[:40]}...'" if message and len(message) > 40 else f": '{message}'" if message else "")) print(f"\n⚡ Interrupt requested" + (f": '{message[:40]}...'" if message and len(message) > 40 else f": '{message}'" if message else ""))
@ -2346,8 +2346,8 @@ class AIAgent:
if self._session_db: if self._session_db:
try: try:
self._session_db.update_system_prompt(self.session_id, self._cached_system_prompt) self._session_db.update_system_prompt(self.session_id, self._cached_system_prompt)
except Exception: except Exception as e:
pass logger.debug("Session DB update_system_prompt failed: %s", e)
active_system_prompt = self._cached_system_prompt active_system_prompt = self._cached_system_prompt
@ -2355,8 +2355,8 @@ class AIAgent:
if self._session_db: if self._session_db:
try: try:
self._session_db.append_message(self.session_id, "user", user_message) self._session_db.append_message(self.session_id, "user", user_message)
except Exception: except Exception as e:
pass logger.debug("Session DB append_message failed: %s", e)
# Main conversation loop # Main conversation loop
api_call_count = 0 api_call_count = 0
@ -2743,8 +2743,8 @@ class AIAgent:
parent_session_id=old_session_id, parent_session_id=old_session_id,
) )
self._session_db.update_system_prompt(self.session_id, active_system_prompt) self._session_db.update_system_prompt(self.session_id, active_system_prompt)
except Exception: except Exception as e:
pass logger.debug("Session DB compression split failed: %s", e)
print(f"{self.log_prefix} 🗜️ Compressed {original_len}{len(messages)} messages, retrying...") print(f"{self.log_prefix} 🗜️ Compressed {original_len}{len(messages)} messages, retrying...")
continue # Retry with compressed messages continue # Retry with compressed messages
else: else:
@ -3175,8 +3175,8 @@ class AIAgent:
parent_session_id=old_session_id, parent_session_id=old_session_id,
) )
self._session_db.update_system_prompt(self.session_id, active_system_prompt) self._session_db.update_system_prompt(self.session_id, active_system_prompt)
except Exception: except Exception as e:
pass logger.debug("Session DB compression split failed: %s", e)
# Save session log incrementally (so progress is visible even if interrupted) # Save session log incrementally (so progress is visible even if interrupted)
self._session_messages = messages self._session_messages = messages

View file

@ -31,6 +31,8 @@ import uuid
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
# Availability gate: UDS requires a POSIX OS # Availability gate: UDS requires a POSIX OS
logger = logging.getLogger(__name__)
SANDBOX_AVAILABLE = sys.platform != "win32" SANDBOX_AVAILABLE = sys.platform != "win32"
# The 7 tools allowed inside the sandbox. The intersection of this list # The 7 tools allowed inside the sandbox. The intersection of this list
@ -488,8 +490,8 @@ def execute_code(
try: try:
import shutil import shutil
shutil.rmtree(tmpdir, ignore_errors=True) shutil.rmtree(tmpdir, ignore_errors=True)
except Exception: except Exception as e:
pass logger.debug("Could not clean temp dir: %s", e)
try: try:
os.unlink(sock_path) os.unlink(sock_path)
except OSError: except OSError:
@ -503,8 +505,8 @@ def _kill_process_group(proc, escalate: bool = False):
except (ProcessLookupError, PermissionError): except (ProcessLookupError, PermissionError):
try: try:
proc.kill() proc.kill()
except Exception: except Exception as e:
pass logger.debug("Could not kill process: %s", e)
if escalate: if escalate:
# Give the process 5s to exit after SIGTERM, then SIGKILL # Give the process 5s to exit after SIGTERM, then SIGKILL
@ -516,8 +518,8 @@ def _kill_process_group(proc, escalate: bool = False):
except (ProcessLookupError, PermissionError): except (ProcessLookupError, PermissionError):
try: try:
proc.kill() proc.kill()
except Exception: except Exception as e:
pass logger.debug("Could not kill process: %s", e)
def _load_config() -> dict: def _load_config() -> dict:

View file

@ -277,14 +277,14 @@ class ProcessRegistry:
session.output_buffer += chunk session.output_buffer += chunk
if len(session.output_buffer) > session.max_output_chars: if len(session.output_buffer) > session.max_output_chars:
session.output_buffer = session.output_buffer[-session.max_output_chars:] session.output_buffer = session.output_buffer[-session.max_output_chars:]
except Exception: except Exception as e:
pass logger.debug("Process stdout reader ended: %s", e)
# Process exited # Process exited
try: try:
session.process.wait(timeout=5) session.process.wait(timeout=5)
except Exception: except Exception as e:
pass logger.debug("Process wait timed out or failed: %s", e)
session.exited = True session.exited = True
session.exit_code = session.process.returncode session.exit_code = session.process.returncode
self._move_to_finished(session) self._move_to_finished(session)
@ -351,14 +351,14 @@ class ProcessRegistry:
break break
except Exception: except Exception:
break break
except Exception: except Exception as e:
pass logger.debug("PTY stdout reader ended: %s", e)
# Process exited # Process exited
try: try:
pty.wait() pty.wait()
except Exception: except Exception as e:
pass logger.debug("PTY wait timed out or failed: %s", e)
session.exited = True session.exited = True
session.exit_code = pty.exitstatus if hasattr(pty, 'exitstatus') else -1 session.exit_code = pty.exitstatus if hasattr(pty, 'exitstatus') else -1
self._move_to_finished(session) self._move_to_finished(session)
@ -719,8 +719,8 @@ class ProcessRegistry:
# Clear the checkpoint (will be rewritten as processes finish) # Clear the checkpoint (will be rewritten as processes finish)
try: try:
CHECKPOINT_PATH.write_text("[]", encoding="utf-8") CHECKPOINT_PATH.write_text("[]", encoding="utf-8")
except Exception: except Exception as e:
pass logger.debug("Could not write checkpoint file: %s", e)
return recovered return recovered

View file

@ -154,8 +154,8 @@ class GitHubAuth:
) )
if result.returncode == 0 and result.stdout.strip(): if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip() return result.stdout.strip()
except (FileNotFoundError, subprocess.TimeoutExpired): except (FileNotFoundError, subprocess.TimeoutExpired) as e:
pass logger.debug("gh CLI token lookup failed: %s", e)
return None return None
def _try_github_app(self) -> Optional[str]: def _try_github_app(self) -> Optional[str]:
@ -438,8 +438,8 @@ class GitHubSource(SkillSource):
) )
if resp.status_code == 200: if resp.status_code == 200:
return resp.text return resp.text
except httpx.HTTPError: except httpx.HTTPError as e:
pass logger.debug("GitHub contents API fetch failed: %s", e)
return None return None
def _read_cache(self, key: str) -> Optional[list]: def _read_cache(self, key: str) -> Optional[list]:
@ -461,8 +461,8 @@ class GitHubSource(SkillSource):
cache_file = INDEX_CACHE_DIR / f"{key}.json" cache_file = INDEX_CACHE_DIR / f"{key}.json"
try: try:
cache_file.write_text(json.dumps(data, ensure_ascii=False)) cache_file.write_text(json.dumps(data, ensure_ascii=False))
except OSError: except OSError as e:
pass logger.debug("Could not write cache: %s", e)
@staticmethod @staticmethod
def _meta_to_dict(meta: SkillMeta) -> dict: def _meta_to_dict(meta: SkillMeta) -> dict:
@ -826,8 +826,8 @@ class LobeHubSource(SkillSource):
resp = httpx.get(url, timeout=15) resp = httpx.get(url, timeout=15)
if resp.status_code == 200: if resp.status_code == 200:
return resp.json() return resp.json()
except (httpx.HTTPError, json.JSONDecodeError): except (httpx.HTTPError, json.JSONDecodeError) as e:
pass logger.debug("LobeHub agent fetch failed: %s", e)
return None return None
@staticmethod @staticmethod
@ -890,8 +890,8 @@ def _write_index_cache(key: str, data: Any) -> None:
cache_file = INDEX_CACHE_DIR / f"{key}.json" cache_file = INDEX_CACHE_DIR / f"{key}.json"
try: try:
cache_file.write_text(json.dumps(data, ensure_ascii=False, default=str)) cache_file.write_text(json.dumps(data, ensure_ascii=False, default=str))
except OSError: except OSError as e:
pass logger.debug("Could not write cache: %s", e)
def _skill_meta_to_dict(meta: SkillMeta) -> dict: def _skill_meta_to_dict(meta: SkillMeta) -> dict:
@ -1037,8 +1037,8 @@ def append_audit_log(action: str, skill_name: str, source: str,
try: try:
with open(AUDIT_LOG, "a") as f: with open(AUDIT_LOG, "a") as f:
f.write(line) f.write(line)
except OSError: except OSError as e:
pass logger.debug("Could not write audit log: %s", e)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -13,11 +13,14 @@ newline-delimited list of skill names that have been offered to the user.
""" """
import json import json
import logging
import os import os
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import List, Tuple from typing import List, Tuple
logger = logging.getLogger(__name__)
HERMES_HOME = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes")) HERMES_HOME = Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
SKILLS_DIR = HERMES_HOME / "skills" SKILLS_DIR = HERMES_HOME / "skills"
@ -131,8 +134,8 @@ def sync_skills(quiet: bool = False) -> dict:
try: try:
dest_desc.parent.mkdir(parents=True, exist_ok=True) dest_desc.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(desc_md, dest_desc) shutil.copy2(desc_md, dest_desc)
except (OSError, IOError): except (OSError, IOError) as e:
pass logger.debug("Could not copy %s: %s", desc_md, e)
_write_manifest(manifest) _write_manifest(manifest)

View file

@ -122,8 +122,8 @@ def _convert_to_opus(mp3_path: str) -> Optional[str]:
) )
if os.path.exists(ogg_path) and os.path.getsize(ogg_path) > 0: if os.path.exists(ogg_path) and os.path.getsize(ogg_path) > 0:
return ogg_path return ogg_path
except Exception: except Exception as e:
pass logger.warning("ffmpeg OGG conversion failed: %s", e)
return None return None