Merge pull request '[loop-cycle-8] fix: replace 59 bare except clauses with proper logging (#25)' (#99) from fix/bare-except-clauses into main

This commit is contained in:
2026-03-14 19:08:40 -04:00
31 changed files with 131 additions and 70 deletions

View File

@@ -393,7 +393,8 @@ def check_ollama_model_available(model_name: str) -> bool:
model_name == m or model_name == m.split(":")[0] or m.startswith(model_name)
for m in models
)
except Exception:
except Exception as exc:
_startup_logger.debug("Ollama model check failed: %s", exc)
return False

View File

@@ -510,7 +510,8 @@ async def swarm_live(websocket: WebSocket):
while True:
# Keep connection alive; events are pushed via ws_mgr.broadcast()
await websocket.receive_text()
except Exception:
except Exception as exc:
logger.debug("WebSocket disconnect error: %s", exc)
ws_mgr.disconnect(websocket)
@@ -532,7 +533,8 @@ async def swarm_agents_sidebar():
f"</div>"
)
return "\n".join(lines) if lines else '<div class="mc-muted">No agents configured</div>'
except Exception:
except Exception as exc:
logger.debug("Agents sidebar error: %s", exc)
return '<div class="mc-muted">Agents unavailable</div>'

View File

@@ -5,6 +5,7 @@ to protect state-changing endpoints from cross-site request attacks.
"""
import hmac
import logging
import secrets
from collections.abc import Callable
from functools import wraps
@@ -16,6 +17,8 @@ from starlette.responses import JSONResponse, Response
# Module-level set to track exempt routes
_exempt_routes: set[str] = set()
logger = logging.getLogger(__name__)
def csrf_exempt(endpoint: Callable) -> Callable:
"""Decorator to mark an endpoint as exempt from CSRF validation.
@@ -278,7 +281,8 @@ class CSRFMiddleware(BaseHTTPMiddleware):
form_token = form_data.get(self.form_field)
if form_token and validate_csrf_token(str(form_token), csrf_cookie):
return True
except Exception:
except Exception as exc:
logger.debug("CSRF form parsing error: %s", exc)
# Error parsing form data, treat as invalid
pass

View File

@@ -115,7 +115,8 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
"duration_ms": f"{duration_ms:.0f}",
},
)
except Exception:
except Exception as exc:
logger.debug("Escalation logging error: %s", exc)
pass # never let escalation break the request
# Re-raise the exception

View File

@@ -4,10 +4,14 @@ Adds common security headers to all HTTP responses to improve
application security posture against various attacks.
"""
import logging
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
logger = logging.getLogger(__name__)
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Middleware to add security headers to all responses.
@@ -130,12 +134,8 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""
try:
response = await call_next(request)
except Exception:
import logging
logging.getLogger(__name__).debug(
"Upstream error in security headers middleware", exc_info=True
)
except Exception as exc:
logger.debug("Upstream error in security headers middleware: %s", exc)
from starlette.responses import PlainTextResponse
response = PlainTextResponse("Internal Server Error", status_code=500)

View File

@@ -220,7 +220,8 @@ async def reject_tool(request: Request, approval_id: str):
# Resume so the agent knows the tool was rejected
try:
await continue_chat(pending["run_output"])
except Exception:
except Exception as exc:
logger.warning("Agent tool rejection error: %s", exc)
pass
reject(approval_id)

View File

@@ -27,7 +27,8 @@ async def get_briefing(request: Request):
"""Return today's briefing page (generated or cached)."""
try:
briefing = briefing_engine.get_or_generate()
except Exception:
except Exception as exc:
logger.debug("Briefing generation failed: %s", exc)
logger.exception("Briefing generation failed")
now = datetime.now(UTC)
briefing = Briefing(

View File

@@ -51,7 +51,8 @@ async def api_chat(request: Request):
try:
body = await request.json()
except Exception:
except Exception as exc:
logger.warning("Chat API JSON parse error: %s", exc)
return JSONResponse(status_code=400, content={"error": "Invalid JSON"})
messages = body.get("messages")

View File

@@ -30,8 +30,8 @@ async def experiments_page(request: Request):
history = []
try:
history = get_experiment_history(_workspace())
except Exception:
logger.debug("Failed to load experiment history", exc_info=True)
except Exception as exc:
logger.debug("Failed to load experiment history: %s", exc)
return templates.TemplateResponse(
request,

View File

@@ -52,8 +52,8 @@ async def grok_status(request: Request):
"estimated_cost_sats": backend.stats.estimated_cost_sats,
"errors": backend.stats.errors,
}
except Exception:
logger.debug("Failed to load Grok stats", exc_info=True)
except Exception as exc:
logger.warning("Failed to load Grok stats: %s", exc)
return templates.TemplateResponse(
request,
@@ -94,8 +94,8 @@ async def toggle_grok_mode(request: Request):
tool_name="grok_mode_toggle",
success=True,
)
except Exception:
logger.debug("Failed to log Grok toggle to Spark", exc_info=True)
except Exception as exc:
logger.warning("Failed to log Grok toggle to Spark: %s", exc)
return HTMLResponse(
_render_toggle_card(_grok_mode_active),
@@ -128,8 +128,8 @@ def _run_grok_query(message: str) -> dict:
sats = min(settings.grok_max_sats_per_query, 100)
ln.create_invoice(sats, f"Grok: {message[:50]}")
invoice_note = f" | {sats} sats"
except Exception:
logger.debug("Lightning invoice creation failed", exc_info=True)
except Exception as exc:
logger.warning("Lightning invoice creation failed: %s", exc)
try:
result = backend.run(message)

View File

@@ -76,8 +76,8 @@ def _check_ollama_sync() -> DependencyStatus:
sovereignty_score=10,
details={"url": settings.ollama_url, "model": settings.ollama_model},
)
except Exception:
logger.debug("Ollama health check failed", exc_info=True)
except Exception as exc:
logger.debug("Ollama health check failed: %s", exc)
return DependencyStatus(
name="Ollama AI",
@@ -101,7 +101,8 @@ async def _check_ollama() -> DependencyStatus:
try:
result = await asyncio.to_thread(_check_ollama_sync)
except Exception:
except Exception as exc:
logger.debug("Ollama async check failed: %s", exc)
result = DependencyStatus(
name="Ollama AI",
status="unavailable",

View File

@@ -144,5 +144,6 @@ async def api_notifications():
for e in events
]
)
except Exception:
except Exception as exc:
logger.debug("System events fetch error: %s", exc)
return JSONResponse([])

View File

@@ -43,7 +43,8 @@ async def tts_status():
"available": voice_tts.available,
"voices": voice_tts.get_voices() if voice_tts.available else [],
}
except Exception:
except Exception as exc:
logger.debug("Voice config error: %s", exc)
return {"available": False, "voices": []}
@@ -139,7 +140,8 @@ async def process_voice_input(
if voice_tts.available:
voice_tts.speak(response_text)
except Exception:
except Exception as exc:
logger.debug("Voice TTS error: %s", exc)
pass
return {

View File

@@ -87,7 +87,8 @@ def _get_git_context() -> dict:
).stdout.strip()
return {"branch": branch, "commit": commit}
except Exception:
except Exception as exc:
logger.warning("Git info capture error: %s", exc)
return {"branch": "unknown", "commit": "unknown"}
@@ -199,7 +200,8 @@ def capture_error(
"title": title[:100],
},
)
except Exception:
except Exception as exc:
logger.warning("Bug report screenshot error: %s", exc)
pass
except Exception as task_exc:
@@ -214,7 +216,8 @@ def capture_error(
message=f"{type(exc).__name__} in {source}: {str(exc)[:80]}",
category="system",
)
except Exception:
except Exception as exc:
logger.warning("Bug report notification error: %s", exc)
pass
# 4. Record in session logger
@@ -226,7 +229,8 @@ def capture_error(
error=f"{type(exc).__name__}: {str(exc)}",
context=source,
)
except Exception:
except Exception as exc:
logger.warning("Bug report session logging error: %s", exc)
pass
return task_id

View File

@@ -304,7 +304,8 @@ class CascadeRouter:
url = provider.url or "http://localhost:11434"
response = requests.get(f"{url}/api/tags", timeout=5)
return response.status_code == 200
except Exception:
except Exception as exc:
logger.debug("Ollama provider check error: %s", exc)
return False
elif provider.type == "airllm":

View File

@@ -54,7 +54,8 @@ class WebSocketManager:
for event in list(self._event_history)[-20:]:
try:
await websocket.send_text(event.to_json())
except Exception:
except Exception as exc:
logger.warning("WebSocket history send error: %s", exc)
break
def disconnect(self, websocket: WebSocket) -> None:
@@ -83,8 +84,8 @@ class WebSocketManager:
await ws.send_text(message)
except ConnectionError:
disconnected.append(ws)
except Exception:
logger.warning("Unexpected WebSocket send error", exc_info=True)
except Exception as exc:
logger.warning("Unexpected WebSocket send error: %s", exc)
disconnected.append(ws)
# Clean up dead connections
@@ -156,7 +157,8 @@ class WebSocketManager:
try:
await ws.send_text(message)
count += 1
except Exception:
except Exception as exc:
logger.warning("WebSocket direct send error: %s", exc)
disconnected.append(ws)
# Clean up dead connections

View File

@@ -87,7 +87,8 @@ if _DISCORD_UI_AVAILABLE:
await action["target"].send(
f"Action `{action['tool_name']}` timed out and was auto-rejected."
)
except Exception:
except Exception as exc:
logger.warning("Discord action timeout message error: %s", exc)
pass
@@ -186,7 +187,8 @@ class DiscordVendor(ChatPlatform):
if self._client and not self._client.is_closed():
try:
await self._client.close()
except Exception:
except Exception as exc:
logger.warning("Discord client close error: %s", exc)
pass
self._client = None
@@ -330,7 +332,8 @@ class DiscordVendor(ChatPlatform):
if settings.discord_token:
return settings.discord_token
except Exception:
except Exception as exc:
logger.warning("Discord token load error: %s", exc)
pass
# 2. Fall back to state file (set via /discord/setup endpoint)
@@ -458,7 +461,8 @@ class DiscordVendor(ChatPlatform):
req.reject(note="User rejected from Discord")
try:
await continue_chat(action["run_output"], action.get("session_id"))
except Exception:
except Exception as exc:
logger.warning("Discord continue chat error: %s", exc)
pass
await interaction.response.send_message(

View File

@@ -56,7 +56,8 @@ class TelegramBot:
from config import settings
return settings.telegram_token or None
except Exception:
except Exception as exc:
logger.warning("Telegram token load error: %s", exc)
return None
def save_token(self, token: str) -> None:

View File

@@ -358,7 +358,8 @@ def get_spark_engine() -> SparkEngine:
from config import settings
_spark_engine = SparkEngine(enabled=settings.spark_enabled)
except Exception:
except Exception as exc:
logger.debug("Spark engine settings load error: %s", exc)
_spark_engine = SparkEngine(enabled=True)
return _spark_engine

View File

@@ -10,12 +10,15 @@ spark_events — raw event log (every swarm event)
spark_memories — consolidated insights extracted from event patterns
"""
import logging
import sqlite3
import uuid
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
logger = logging.getLogger(__name__)
DB_PATH = Path("data/spark.db")
# Importance thresholds
@@ -170,7 +173,8 @@ def record_event(
task_id=task_id or "",
agent_id=agent_id or "",
)
except Exception:
except Exception as exc:
logger.debug("Spark event log error: %s", exc)
pass # Graceful — don't break spark if event_log is unavailable
return event_id

View File

@@ -358,7 +358,8 @@ class TimmyWithMemory:
if name:
self.memory.update_user_fact("Name", name)
self.memory.record_decision(f"Learned user's name: {name}")
except Exception:
except Exception as exc:
logger.warning("User name extraction failed: %s", exc)
pass # Best-effort extraction
def end_session(self, summary: str = "Session completed") -> None:

View File

@@ -332,5 +332,6 @@ async def _broadcast_progress(event: str, data: dict) -> None:
from infrastructure.ws_manager.handler import ws_manager
await ws_manager.broadcast(event, data)
except Exception:
except Exception as exc:
logger.warning("Agentic loop broadcast failed: %s", exc)
logger.debug("Agentic loop: WS broadcast failed for %s", event)

View File

@@ -414,7 +414,8 @@ def grok_available() -> bool:
from config import settings
return settings.grok_enabled and bool(settings.xai_api_key)
except Exception:
except Exception as exc:
logger.warning("Backend check failed (grok_available): %s", exc)
return False
@@ -566,5 +567,6 @@ def claude_available() -> bool:
from config import settings
return bool(settings.anthropic_api_key)
except Exception:
except Exception as exc:
logger.warning("Backend check failed (claude_available): %s", exc)
return False

View File

@@ -259,7 +259,8 @@ def interview(
from timmy.mcp_tools import close_mcp_sessions
loop.run_until_complete(close_mcp_sessions())
except Exception:
except Exception as exc:
logger.warning("MCP session close failed: %s", exc)
pass
loop.close()

View File

@@ -262,7 +262,8 @@ def capture_error(exc, **kwargs):
from infrastructure.error_capture import capture_error as _capture
return _capture(exc, **kwargs)
except Exception:
except Exception as capture_exc:
logger.debug("Failed to capture error: %s", capture_exc)
logger.debug("Failed to capture error", exc_info=True)

View File

@@ -5,11 +5,14 @@ to retrieve relevant context from conversation history.
"""
import json
import logging
import sqlite3
import uuid
from dataclasses import dataclass, field
from datetime import UTC, datetime
logger = logging.getLogger(__name__)
def _check_embedding_model() -> bool | None:
"""Check if the canonical embedding model is available."""
@@ -18,7 +21,8 @@ def _check_embedding_model() -> bool | None:
model = _get_embedding_model()
return model is not None and model is not False
except Exception:
except Exception as exc:
logger.debug("Embedding model check failed: %s", exc)
return None

View File

@@ -177,7 +177,8 @@ class ThinkingEngine:
latest = self.get_recent_thoughts(limit=1)
if latest:
self._last_thought_id = latest[0].id
except Exception:
except Exception as exc:
logger.debug("Failed to load recent thought: %s", exc)
pass # Fresh start if DB doesn't exist yet
async def think_once(self, prompt: str | None = None) -> Thought | None:
@@ -578,7 +579,8 @@ class ThinkingEngine:
).fetchone()["c"]
conn.close()
parts.append(f"Thoughts today: {count}")
except Exception:
except Exception as exc:
logger.debug("Thought count query failed: %s", exc)
pass
# Recent chat activity (in-memory, no I/O)
@@ -592,7 +594,8 @@ class ThinkingEngine:
parts.append(f'Last chat ({last.role}): "{last.content[:80]}"')
else:
parts.append("No chat messages this session")
except Exception:
except Exception as exc:
logger.debug("Chat activity query failed: %s", exc)
pass
# Task queue (lightweight DB query)
@@ -609,7 +612,8 @@ class ThinkingEngine:
f"Tasks: {running} running, {pending} pending, "
f"{done} completed, {failed} failed"
)
except Exception:
except Exception as exc:
logger.debug("Task queue query failed: %s", exc)
pass
return "\n".join(parts) if parts else ""

View File

@@ -440,7 +440,8 @@ def consult_grok(query: str) -> str:
tool_name="consult_grok",
success=True,
)
except Exception:
except Exception as exc:
logger.warning("Tool execution failed (consult_grok logging): %s", exc)
pass
# Generate Lightning invoice for monetization (unless free mode)
@@ -453,7 +454,8 @@ def consult_grok(query: str) -> str:
sats = min(settings.grok_max_sats_per_query, 100)
inv = ln.create_invoice(sats, f"Grok query: {query[:50]}")
invoice_info = f"\n[Lightning invoice: {sats} sats — {inv.payment_request[:40]}...]"
except Exception:
except Exception as exc:
logger.warning("Tool execution failed (Lightning invoice): %s", exc)
pass
result = backend.run(query)
@@ -512,7 +514,8 @@ def create_full_toolkit(base_dir: str | Path | None = None):
if grok_available():
toolkit.register(consult_grok, name="consult_grok")
logger.info("Grok consultation tool registered")
except Exception:
except Exception as exc:
logger.warning("Tool execution failed (Grok registration): %s", exc)
logger.debug("Grok tool not available")
# Memory search, write, and forget — persistent recall across all channels
@@ -523,7 +526,8 @@ def create_full_toolkit(base_dir: str | Path | None = None):
toolkit.register(memory_write, name="memory_write")
toolkit.register(memory_read, name="memory_read")
toolkit.register(memory_forget, name="memory_forget")
except Exception:
except Exception as exc:
logger.warning("Tool execution failed (Memory tools registration): %s", exc)
logger.debug("Memory tools not available")
# Agentic loop — background multi-step task execution
@@ -569,7 +573,8 @@ def create_full_toolkit(base_dir: str | Path | None = None):
)
toolkit.register(plan_and_execute, name="plan_and_execute")
except Exception:
except Exception as exc:
logger.warning("Tool execution failed (plan_and_execute registration): %s", exc)
logger.debug("plan_and_execute tool not available")
# System introspection - query runtime environment (sovereign self-knowledge)
@@ -579,7 +584,8 @@ def create_full_toolkit(base_dir: str | Path | None = None):
toolkit.register(get_system_info, name="get_system_info")
toolkit.register(check_ollama_health, name="check_ollama_health")
toolkit.register(get_memory_status, name="get_memory_status")
except Exception:
except Exception as exc:
logger.warning("Tool execution failed (Introspection tools registration): %s", exc)
logger.debug("Introspection tools not available")
# Inter-agent delegation - dispatch tasks to swarm agents
@@ -588,7 +594,8 @@ def create_full_toolkit(base_dir: str | Path | None = None):
toolkit.register(delegate_task, name="delegate_task")
toolkit.register(list_swarm_agents, name="list_swarm_agents")
except Exception:
except Exception as exc:
logger.warning("Tool execution failed (Delegation tools registration): %s", exc)
logger.debug("Delegation tools not available")
# Gitea issue management is now provided by the gitea-mcp server

View File

@@ -89,7 +89,8 @@ def _get_ollama_model() -> str:
name = model.get("name", "")
if name == configured or name == f"{configured}:latest":
return configured
except Exception:
except Exception as exc:
logger.debug("Model validation failed: %s", exc)
pass
# Fallback to configured model
@@ -186,7 +187,8 @@ def get_memory_status() -> dict[str, Any]:
tier3_info["available"] = True
tier3_info["vector_count"] = count[0] if count else 0
conn.close()
except Exception:
except Exception as exc:
logger.debug("Memory status query failed: %s", exc)
pass
# Self-coding journal stats
@@ -212,7 +214,8 @@ def get_memory_status() -> dict[str, Any]:
"success_rate": round(counts.get("success", 0) / total, 2) if total else 0,
}
conn.close()
except Exception:
except Exception as exc:
logger.debug("Journal stats query failed: %s", exc)
pass
return {
@@ -303,7 +306,8 @@ def get_live_system_status() -> dict[str, Any]:
uptime = (datetime.now(UTC) - _START_TIME).total_seconds()
result["uptime_seconds"] = int(uptime)
except Exception:
except Exception as exc:
logger.debug("Uptime calculation failed: %s", exc)
result["uptime_seconds"] = None
# Discord status
@@ -311,7 +315,8 @@ def get_live_system_status() -> dict[str, Any]:
from integrations.chat_bridge.vendors.discord import discord_bot
result["discord"] = {"state": discord_bot.state.name}
except Exception:
except Exception as exc:
logger.debug("Discord status check failed: %s", exc)
result["discord"] = {"state": "unknown"}
result["timestamp"] = datetime.now(UTC).isoformat()

View File

@@ -465,14 +465,16 @@ class VoiceLoop:
try:
self._loop.run_until_complete(self._loop.shutdown_asyncgens())
except Exception:
except Exception as exc:
logger.debug("Shutdown asyncgens failed: %s", exc)
pass
with warnings.catch_warnings():
warnings.simplefilter("ignore", RuntimeWarning)
try:
self._loop.close()
except Exception:
except Exception as exc:
logger.debug("Loop close failed: %s", exc)
pass
self._loop = None

View File

@@ -87,7 +87,8 @@ class VoiceTTS:
{"id": v.id, "name": v.name, "languages": getattr(v, "languages", [])}
for v in voices
]
except Exception:
except Exception as exc:
logger.debug("Voice list retrieval failed: %s", exc)
return []
def set_voice(self, voice_id: str) -> None: