refactor(errors): replace silent exception handling in console and server

This commit is contained in:
somew1nd committed 2026-09-01 17:57:15 -07:00
1 parent 41deb48005
commit c76c9bb7e8
20 files changed
+267 -107

No files matched your search

+15 -3
View File
@@ -15,8 +15,11 @@
import asyncio
from contextlib import suppress
import json
import logging
from typing import Any
logger = logging.getLogger(__name__)
try:
from admin_console.core.state import state
except ImportError:
@@ -76,7 +79,8 @@ class IPCService:
if isinstance(payload, str):
try:
payload = json.loads(payload)
except Exception:
except ValueError:
# Non-JSON payload string: cleaned as-is below.
pass
if tr_type in ("tool", "action") and isinstance(payload, dict):
@@ -102,7 +106,8 @@ class IPCService:
if isinstance(pl, str):
try:
pl = json.loads(pl)
except Exception:
except ValueError:
# Non-JSON payload string: cleaned as-is below.
pass
if tr_dict.get("type") == "llm_call":
@@ -190,7 +195,14 @@ class IPCService:
try:
cb(event_type, data)
except Exception:
pass
# One broken subscriber must not block the
# others, but a silent drop hides it entirely.
logger.warning(
"IPC subscriber %r failed for event %s",
cb,
event_type,
exc_info=True,
)
except (ValueError, UnicodeDecodeError, TypeError) as e:
print(f"IPC parse error: {e}")
except asyncio.CancelledError:
+19 -8
View File
@@ -15,6 +15,7 @@
import base64
import hashlib
import json
import logging
import os
from pathlib import Path
import subprocess
@@ -25,6 +26,8 @@ from fastapi import HTTPException
from artemis.config import IMAGES_DIR, TRACES_PATH, WORKSPACE_ROOT
logger = logging.getLogger(__name__)
class MediaService:
"""Service handling media indexing, local file security, payload unwrapping and notes/plans."""
@@ -76,7 +79,9 @@ class MediaService:
str(result),
)
return result
except Exception:
except OSError:
# Filesystem probe failed (missing/locked file): serve the
# original path and let the media endpoint report the error.
pass
return p
@@ -167,8 +172,9 @@ class MediaService:
url = cls.path_to_video_url(item)
idx[item.stem] = url
idx[item.name] = url
except Exception:
pass
except OSError as exc:
# Unreadable directory entry: index the rest of the tree.
logger.warning("Video index scan failed under %s: %s", d, exc)
return idx
@classmethod
@@ -219,7 +225,8 @@ class MediaService:
item = cls.ensure_browser_playable_video(item)
v_url = cls.path_to_video_url(item)
break
except Exception:
except OSError:
# Unreadable session dir: no recording to resolve.
pass
if not v_url and row_dict.get("initial_goal"):
@@ -274,7 +281,8 @@ class MediaService:
obj.startswith("[") and obj.endswith("]")
):
return MediaService.unwrap_payload(json.loads(obj))
except Exception:
except ValueError:
# Looks like JSON but is not: treat it as a plain string.
pass
# Check if the string is a Base64 image
@@ -284,7 +292,8 @@ class MediaService:
try:
_, base64_data = obj.split(";base64,", 1)
is_base64_img = True
except Exception:
except ValueError:
# Malformed data URI: fall through to plain-string handling.
pass
elif obj.startswith("iVBORw0KGgo") or obj.startswith("/9j/"):
is_base64_img = True
@@ -301,8 +310,10 @@ class MediaService:
image_path.write_bytes(image_bytes)
return f"image://{image_hash}"
except Exception:
pass
except (ValueError, OSError) as exc:
# Invalid base64 or image cache write failure: fall back
# to the truncated raw string below.
logger.debug("Could not persist inline image: %s", exc)
if len(obj) > 2000:
preview_start = obj[:100].replace("\n", " ")
+11 -4
View File
@@ -13,9 +13,12 @@
# limitations under the License.
import json
import logging
import time
from typing import Any
logger = logging.getLogger(__name__)
class ModelService:
"""Service handling agent architecture profile detection and metadata formatting."""
@@ -44,8 +47,10 @@ class ModelService:
elif llm_cfg and llm_cfg.default:
provider = str(llm_cfg.default.provider)
model_id = str(llm_cfg.default.model)
except Exception:
pass
except Exception as exc:
# Unreadable/invalid LLM config: fall back to display defaults
# (cached for the TTL, so this logs at most once per window).
logger.warning("Could not resolve LLM config for display: %s", exc)
cls._llm_info_cache = (now, provider, model_id)
return provider, model_id
@@ -94,7 +99,8 @@ class ModelService:
return "pro"
elif "flash" in p_str:
return "flash"
except Exception:
except (ValueError, TypeError):
# Malformed device_info JSON: try the other profile sources.
pass
if (row_dict.get("status") == "running") and running_profile:
@@ -132,7 +138,8 @@ class ModelService:
for x in ("planner", "validator", "operator", "checker")
):
return "pro"
except Exception:
except (ValueError, TypeError):
# Malformed trace payload JSON: skip this trace.
pass
return None
@@ -15,6 +15,7 @@
import asyncio
import codecs
from datetime import datetime
import logging
import os
import psutil
import shutil
@@ -48,6 +49,8 @@ from artemis.runtime import (
trace_store,
)
logger = logging.getLogger(__name__)
class TaskQueueService:
"""Service managing FIFO task execution, background worker, subprocess lifecycle,
@@ -76,7 +79,14 @@ class TaskQueueService:
try:
cb(event_type, data)
except Exception:
pass
# One broken subscriber must not block the others, but a
# silent drop hides it entirely.
logger.warning(
"Event subscriber %r failed for event %s",
cb,
event_type,
exc_info=True,
)
@classmethod
def _broadcast_startup_progress(
@@ -194,7 +204,9 @@ class TaskQueueService:
if log_file is not None:
try:
log_file.write(text)
except Exception:
except (OSError, ValueError):
# Best-effort tee into stdout.log; console output above
# already carried the text.
pass
try:
@@ -214,7 +226,8 @@ class TaskQueueService:
if log_file is not None:
try:
log_file.close()
except Exception:
except OSError:
# Flush-on-close of the best-effort tee failed; nothing to do.
pass
@staticmethod
@@ -265,7 +278,8 @@ class TaskQueueService:
return proc.returncode if proc.returncode is not None else -15
except (psutil.NoSuchProcess, ProcessLookupError):
return proc.returncode if proc.returncode is not None else -15
except Exception:
except psutil.Error:
# Transient probe failure (e.g. AccessDenied): keep waiting.
pass
else:
return proc.returncode if proc.returncode is not None else -15
@@ -279,7 +293,9 @@ class TaskQueueService:
state.worker_task = loop.create_task(cls.queue_worker())
try:
state.wake_event.set()
except Exception:
except AttributeError:
# Partially initialized state (unit tests): the worker
# loop polls anyway.
pass
except RuntimeError:
pass
@@ -515,8 +531,11 @@ class TaskQueueService:
if st:
st["pid"] = proc.pid
trace_store.write_status(str(sess_id), st)
except Exception:
pass
except OSError as exc:
# Status probes fall back to DB PID bookkeeping, but note it.
print(
f"[QueueWorker] Could not record worker pid in status.json for {sess_id}: {exc}"
)
cls._broadcast_startup_progress(
sess_id, "process_ready", "Execution process started"
)
@@ -583,8 +602,12 @@ class TaskQueueService:
str(sess_id),
canonical_mcp_status,
)
except Exception:
pass
except OSError as exc:
# A stale status.json would leave MCP pollers seeing
# "running" forever, so surface the failure.
print(
f"[QueueWorker] Could not persist terminal MCP status for {sess_id}: {exc}"
)
rec_info = session_repo.get_video_recording_for_session(sess_id)
rec_status = (rec_info or {}).get("status")
recovered_video_path = None
@@ -848,8 +871,9 @@ class TaskQueueService:
active_owners: dict[str, Any] = {}
try:
active_owners = DeviceExecutionLock.get_active_owners()
except Exception:
pass
except OSError as exc:
# Unreadable lock dir: fall back to the single-owner probe below.
print(f"[stop_tasks] Could not enumerate device owners: {exc}")
fallback = DeviceExecutionLock.get_active_owner()
if fallback and not active_owners:
active_owners["default"] = fallback
@@ -872,8 +896,10 @@ class TaskQueueService:
"cancelled",
error="Task stopped from the Artemis frontend.",
)
except Exception:
pass
except OSError as exc:
print(
f"[stop_tasks] Could not persist cancelled MCP status for {sid}: {exc}"
)
cls._broadcast_event(
"session_ended",
{
@@ -895,7 +921,8 @@ class TaskQueueService:
if run_proc is not None and run_proc.returncode is None:
try:
run_proc.kill()
except Exception:
except (ProcessLookupError, OSError):
# Process already exited between the check and the kill.
pass
# active_runs entries are popped by each run's finalizer once the
# process exit is observed; clearing them here would free the device
@@ -903,7 +930,8 @@ class TaskQueueService:
if state.current_process:
try:
state.current_process.kill()
except Exception:
except (ProcessLookupError, OSError):
# Process already exited between the check and the kill.
pass
state.current_process = None
@@ -915,7 +943,9 @@ class TaskQueueService:
if PAUSE_FILE.exists():
try:
PAUSE_FILE.unlink()
except Exception:
except OSError:
# Best-effort cleanup of the pause marker; a leftover file
# only pauses until the next resume request.
pass
cls.ensure_worker_running()
@@ -926,8 +956,9 @@ class TaskQueueService:
active_owners = {}
try:
active_owners = DeviceExecutionLock.get_active_owners()
except Exception:
pass
except OSError as exc:
# Unreadable lock dir: the local-process fallbacks below still apply.
print(f"[stop_tasks] Could not enumerate device owners: {exc}")
owner = None
if target_sid:
@@ -1066,7 +1097,8 @@ class TaskQueueService:
try:
local_proc.kill()
stopped = True
except Exception:
except (ProcessLookupError, OSError):
# Process already exited; terminate_tree above may have got it.
pass
stopped = True
is_local_owner = True
@@ -1097,8 +1129,11 @@ class TaskQueueService:
if row_pid and session_repo.process_is_alive(row_pid):
try:
process_supervisor.terminate_tree(int(row_pid))
except Exception:
pass
except Exception as exc:
# Report it: a surviving worker keeps the device busy.
print(
f"[stop_tasks] Could not terminate worker pid {row_pid}: {exc}"
)
DeviceExecutionLock.cleanup_stale_locks()
stopped = True
@@ -1176,7 +1211,9 @@ class TaskQueueService:
if PAUSE_FILE.exists():
try:
PAUSE_FILE.unlink()
except Exception:
except OSError:
# Best-effort cleanup of the pause marker; a leftover file
# only pauses until the next resume request.
pass
cls.ensure_worker_running()