refactor(errors): replace silent exception handling in core packages
This commit is contained in:
1 parent
584bb2d09c
commit
47e7cb6620
21 files changed
+132
-58
No files matched your search
@@ -467,7 +467,9 @@ class EmulatorManager:
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=5,
|
||||
)
|
||||
except Exception:
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
# Graceful `adb emu kill` failed or timed out; the direct
|
||||
# process terminate/kill below still stops the emulator.
|
||||
pass
|
||||
|
||||
if self._proc and self._proc.poll() is None:
|
||||
|
||||
@@ -194,8 +194,12 @@ class ReadinessEngine:
|
||||
if adb_result and adb_result.metadata.get("active_device"):
|
||||
try:
|
||||
active_device = DeviceInfo(**adb_result.metadata["active_device"])
|
||||
except Exception:
|
||||
pass
|
||||
except (TypeError, ValueError) as exc:
|
||||
# Includes pydantic ValidationError (a ValueError subclass).
|
||||
logger.warning(
|
||||
f"Malformed active_device metadata from ADB probe; readiness"
|
||||
f" report will omit the active device: {exc}"
|
||||
)
|
||||
|
||||
return SystemReadinessReport(
|
||||
overall_ready=overall_ready,
|
||||
|
||||
@@ -355,8 +355,9 @@ class AndroidAdbDriver(BaseDeviceDriver):
|
||||
broadcast_cmd = f"am broadcast -a ADB_INPUT_B64 --es msg '{b64_text}'"
|
||||
await asyncio.to_thread(self.device.shell, broadcast_cmd)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
# ADBKeyboard probe/broadcast failed; fall through to native input.
|
||||
logger.debug(f"ADBKeyboard IME path failed, falling back to ADB input: {e}")
|
||||
|
||||
# 3. Tier 3: Universal Native ADB input text fallback
|
||||
lines = norm_text.split("\n")
|
||||
@@ -410,8 +411,9 @@ class AndroidAdbDriver(BaseDeviceDriver):
|
||||
app_info = await asyncio.to_thread(self.device.current_app)
|
||||
if app_info and getattr(app_info, "package", None):
|
||||
return app_info.package
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
# current_app is unsupported on some devices; use dumpsys below.
|
||||
logger.debug(f"current_app query failed, falling back to dumpsys: {e}")
|
||||
|
||||
# 2. Structured dumpsys extraction
|
||||
out = await asyncio.to_thread(
|
||||
@@ -467,8 +469,10 @@ class AndroidAdbDriver(BaseDeviceDriver):
|
||||
try:
|
||||
self._scrcpy_process.terminate()
|
||||
await asyncio.wait_for(self._scrcpy_process.wait(), timeout=5.0)
|
||||
except Exception:
|
||||
pass
|
||||
except (ProcessLookupError, OSError, asyncio.TimeoutError) as e:
|
||||
# Already exited, or did not stop within the timeout; a lingering
|
||||
# scrcpy may keep the MKV file locked on Windows.
|
||||
logger.debug(f"scrcpy process did not terminate cleanly: {e}")
|
||||
self._scrcpy_process = None
|
||||
|
||||
mkv = getattr(self, "_recording_mkv_path", None)
|
||||
@@ -493,7 +497,8 @@ class AndroidAdbDriver(BaseDeviceDriver):
|
||||
if mp4.exists() and mp4.stat().st_size > 0:
|
||||
try:
|
||||
mkv.unlink()
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Best-effort cleanup of the intermediate MKV file.
|
||||
pass
|
||||
return str(mp4)
|
||||
except Exception as e:
|
||||
|
||||
@@ -102,8 +102,9 @@ class AndroidInputIME:
|
||||
broadcast_cmd = f"am broadcast -a ADB_INPUT_B64 --es msg '{b64_text}'"
|
||||
await asyncio.to_thread(self.device.shell, broadcast_cmd)
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
# ADBKeyboard probe/broadcast failed; fall through to ADB input.
|
||||
logger.debug(f"ADBKeyboard IME path failed, falling back to ADB input: {e}")
|
||||
|
||||
# 4. Fallback: ADB input text
|
||||
lines = text.split("\n")
|
||||
|
||||
@@ -170,7 +170,8 @@ def doctor_command(
|
||||
state = parts[1]
|
||||
extra = " ".join(parts[2:])
|
||||
devices_info.append((serial, state, extra))
|
||||
except Exception:
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
# adb probe failed or timed out; report as "no devices detected".
|
||||
pass
|
||||
|
||||
if devices_info:
|
||||
|
||||
@@ -78,7 +78,8 @@ def init_command() -> None:
|
||||
serial = parts[0]
|
||||
extra = " ".join(parts[2:])
|
||||
detected_devices.append((serial, extra))
|
||||
except Exception:
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
# adb probe failed or timed out; continue setup with no devices.
|
||||
pass
|
||||
|
||||
selected_serial = ""
|
||||
|
||||
@@ -166,7 +166,8 @@ def _parse_json_lenient(text: str) -> dict | None:
|
||||
try:
|
||||
data = json.loads(text)
|
||||
return data if isinstance(data, dict) else None
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Not strict JSON; fall through to the lenient JSONC cleanup below.
|
||||
pass
|
||||
# Remove // single-line comments
|
||||
cleaned = re.sub(r"//.*", "", text)
|
||||
@@ -215,8 +216,11 @@ def _merge_json_file(
|
||||
try:
|
||||
backup_path.write_text(raw_text, encoding="utf-8")
|
||||
logger.warning(f"Existing JSON in {file_path} could not be parsed; backup created at {backup_path}")
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
logger.warning(
|
||||
f"Existing JSON in {file_path} could not be parsed AND the backup to"
|
||||
f" {backup_path} failed ({exc}); the original content will be overwritten."
|
||||
)
|
||||
|
||||
for remove_path in remove_paths:
|
||||
parent = data
|
||||
@@ -316,8 +320,10 @@ def _merge_codex_toml(file_path: Path, server_config: dict) -> bool:
|
||||
backup_path = file_path.with_suffix(file_path.suffix + ".bak")
|
||||
backup_path.write_text(file_path.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
logger.warning(f"Could not update {file_path}; backup created at {backup_path}: {e}")
|
||||
except Exception:
|
||||
pass
|
||||
except (OSError, UnicodeDecodeError) as backup_exc:
|
||||
logger.warning(
|
||||
f"Could not update {file_path} ({e}); backup also failed: {backup_exc}"
|
||||
)
|
||||
else:
|
||||
logger.warning(f"Could not create Codex MCP config file {file_path}: {e}")
|
||||
return False
|
||||
@@ -394,7 +400,8 @@ def _write_rule_file(file_path: Path, content: str) -> bool:
|
||||
try:
|
||||
if file_path.read_text(encoding="utf-8") == new_text:
|
||||
return True
|
||||
except Exception:
|
||||
except (OSError, UnicodeDecodeError):
|
||||
# Unreadable existing file: skip the no-op check and rewrite it.
|
||||
pass
|
||||
file_path.write_text(new_text, encoding="utf-8")
|
||||
return True
|
||||
@@ -419,7 +426,8 @@ def _write_cursor_mdc(file_path: Path, content: str) -> bool:
|
||||
try:
|
||||
if file_path.read_text(encoding="utf-8") == new_text:
|
||||
return True
|
||||
except Exception:
|
||||
except (OSError, UnicodeDecodeError):
|
||||
# Unreadable existing file: skip the no-op check and rewrite it.
|
||||
pass
|
||||
file_path.write_text(new_text, encoding="utf-8")
|
||||
return True
|
||||
|
||||
@@ -408,8 +408,9 @@ def run_command(
|
||||
host=settings.ADB_HOST or "localhost",
|
||||
port=settings.ADB_PORT or 5037,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Optional cosmetic device-status display; run continues without it.
|
||||
logger.debug(f"Could not create ADB client for device status display: {exc}")
|
||||
|
||||
display_device_status(console, adb_client=adb_client)
|
||||
|
||||
@@ -420,7 +421,9 @@ def run_command(
|
||||
original_sigterm = signal.signal(
|
||||
signal.SIGTERM, lambda s, f: (_ for _ in ()).throw(KeyboardInterrupt())
|
||||
)
|
||||
except Exception:
|
||||
except (ValueError, OSError, RuntimeError):
|
||||
# Not the main thread or platform does not support SIGTERM handlers;
|
||||
# run proceeds without the graceful-cancel hook.
|
||||
pass
|
||||
|
||||
try:
|
||||
@@ -471,7 +474,8 @@ def run_command(
|
||||
try:
|
||||
if original_sigterm is not None and hasattr(signal, "SIGTERM"):
|
||||
signal.signal(signal.SIGTERM, original_sigterm)
|
||||
except Exception:
|
||||
except (ValueError, OSError, RuntimeError):
|
||||
# Best-effort restore of the previous handler on the way out.
|
||||
pass
|
||||
if cancelled:
|
||||
raise SystemExit(130)
|
||||
@@ -265,7 +265,8 @@ def status_command(
|
||||
try:
|
||||
dt = datetime.datetime.fromtimestamp(metadata["started_at"])
|
||||
started_at_str = dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
except (TypeError, ValueError, OSError, OverflowError):
|
||||
# Malformed or out-of-range timestamp in metadata: show "Unknown".
|
||||
pass
|
||||
|
||||
table = Table(show_header=False, box=None, padding=(0, 2))
|
||||
|
||||
@@ -107,7 +107,8 @@ class WindowsPlatformProcess(IPlatformProcess):
|
||||
)
|
||||
if res.returncode == 0:
|
||||
return True
|
||||
except Exception:
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
# taskkill missing, not runnable, or timed out; try psutil below.
|
||||
pass
|
||||
|
||||
# 2. Fallback to psutil
|
||||
@@ -131,12 +132,14 @@ class WindowsPlatformProcess(IPlatformProcess):
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
except (OSError, ValueError):
|
||||
# Stream is detached/closed or does not support reconfigure.
|
||||
pass
|
||||
if hasattr(sys.stderr, "reconfigure"):
|
||||
try:
|
||||
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
except (OSError, ValueError):
|
||||
# Stream is detached/closed or does not support reconfigure.
|
||||
pass
|
||||
os.environ["PYTHONUTF8"] = "1"
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ def spawn_daemon(
|
||||
if log_handle is not subprocess.DEVNULL:
|
||||
try:
|
||||
log_handle.close()
|
||||
except Exception:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -152,7 +152,8 @@ class DevicePool:
|
||||
if proc is not None:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
except (ProcessLookupError, OSError):
|
||||
# Process already exited; nothing left to clean up.
|
||||
pass
|
||||
return None
|
||||
|
||||
@@ -256,7 +257,8 @@ class DevicePool:
|
||||
if proc is not None:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
except (ProcessLookupError, OSError):
|
||||
# Process already exited; nothing left to clean up.
|
||||
pass
|
||||
|
||||
async def warm_up_async(
|
||||
|
||||
@@ -113,8 +113,9 @@ class ProcessSupervisor:
|
||||
logger.debug(f"Failed graceful stop for process {proc}: {e}")
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
except (ProcessLookupError, OSError) as kill_exc:
|
||||
# ProcessLookupError: already exited, nothing to do.
|
||||
logger.debug(f"Failed to kill process {proc}: {kill_exc}")
|
||||
|
||||
|
||||
process_supervisor: ProcessSupervisor = ProcessSupervisor()
|
||||
@@ -26,6 +26,7 @@ def _get_embedded_adb() -> str | None:
|
||||
if hasattr(adbutils, "adb_path"):
|
||||
return adbutils.adb_path()
|
||||
except Exception:
|
||||
# Optional dependency probe: not installed or no bundled binary.
|
||||
pass
|
||||
return None
|
||||
|
||||
@@ -38,6 +39,8 @@ def _get_embedded_ffmpeg() -> str | None:
|
||||
if hasattr(imageio_ffmpeg, "get_ffmpeg_exe"):
|
||||
return imageio_ffmpeg.get_ffmpeg_exe()
|
||||
except Exception:
|
||||
# Optional dependency probe: not installed, or imageio_ffmpeg raises
|
||||
# RuntimeError when it ships no binary for this platform.
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ class ToolchainResolver:
|
||||
self._cache[key] = resolved
|
||||
return resolved
|
||||
except Exception:
|
||||
# Probing an optional third-party embedded binary; any failure
|
||||
# simply means "tool not found" and the caller handles None.
|
||||
pass
|
||||
|
||||
# Do not permanently cache negative lookups so dynamic installs are detected immediately
|
||||
|
||||
@@ -63,7 +63,7 @@ def parse_swipe_parameters(
|
||||
if isinstance(p, (list, tuple)) and len(p) >= 2:
|
||||
try:
|
||||
return [int(float(p[0])), int(float(p[1]))]
|
||||
except Exception:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if isinstance(p, str):
|
||||
nums = re.findall(r"-?\d+(?:\.\d+)?", p)
|
||||
@@ -88,7 +88,7 @@ def parse_swipe_parameters(
|
||||
if len(c) == 4:
|
||||
try:
|
||||
return "coords", [int(float(x)) for x in c], duration_int
|
||||
except Exception:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
elif (
|
||||
len(c) == 2 and isinstance(c[0], (list, tuple)) and isinstance(c[1], (list, tuple))
|
||||
@@ -112,7 +112,7 @@ def parse_swipe_parameters(
|
||||
pt2 = _parse_pt(parsed[1])
|
||||
if pt1 and pt2:
|
||||
return "coords", [pt1[0], pt1[1], pt2[0], pt2[1]], duration_int
|
||||
except Exception:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Regex extract numbers
|
||||
@@ -448,7 +448,9 @@ def normalize_step_actions(step_dict: dict[str, Any]) -> dict[str, Any]:
|
||||
import ast
|
||||
|
||||
seq = ast.literal_eval(seq)
|
||||
except Exception:
|
||||
except (ValueError, TypeError, SyntaxError):
|
||||
# Unparseable sequence string: leave it as-is; the
|
||||
# isinstance check below skips non-list values.
|
||||
pass
|
||||
if isinstance(seq, (list, tuple)):
|
||||
item["sequence"] = list(seq)
|
||||
@@ -481,7 +483,8 @@ def normalize_step_actions(step_dict: dict[str, Any]) -> dict[str, Any]:
|
||||
try:
|
||||
action_taken = json.loads(action_taken)
|
||||
step_dict["action_taken"] = action_taken
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Not JSON: keep the raw string; downstream isinstance checks skip it.
|
||||
pass
|
||||
|
||||
if isinstance(action_taken, dict):
|
||||
|
||||
@@ -43,7 +43,8 @@ def _extract_error_message(resp: httpx.Response) -> str:
|
||||
return str(data["message"])
|
||||
if "detail" in data:
|
||||
return str(data["detail"])
|
||||
except Exception:
|
||||
except ValueError:
|
||||
# Response body is not valid JSON; fall through to the text fallback.
|
||||
pass
|
||||
|
||||
# Fallback to response text or HTTP status
|
||||
|
||||
+44
-15
@@ -20,6 +20,9 @@ from pathlib import Path
|
||||
import re
|
||||
|
||||
from artemis.data_engine.engine import _CURRENT_DATA_ENGINE
|
||||
from artemis.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Centralized Description and Documentation Constants (Single Source of Truth)
|
||||
READ_NOTE_DOCSTRING = (
|
||||
@@ -153,22 +156,30 @@ def save_note_content(base_dir: str | Path, key: str, content: str) -> None:
|
||||
if key == "task_plan" and file_path.exists():
|
||||
try:
|
||||
content_before = file_path.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
except (OSError, UnicodeDecodeError) as exc:
|
||||
logger.warning(
|
||||
"Failed to read previous task_plan content from %s; subgoal"
|
||||
" rename tracking will be skipped for this save: %s",
|
||||
file_path,
|
||||
exc,
|
||||
)
|
||||
|
||||
file_path.write_text(content, encoding="utf-8")
|
||||
|
||||
if key == "task_plan":
|
||||
try:
|
||||
record_subgoal_hash_chain(base_dir, content_before, content)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Hash-chain bookkeeping must never break a note save, but a
|
||||
# missed record can later confuse plan ratchet validation.
|
||||
logger.warning("Failed to record subgoal hash chain for task_plan: %s", exc)
|
||||
|
||||
try:
|
||||
if _CURRENT_DATA_ENGINE:
|
||||
_CURRENT_DATA_ENGINE._publish("note_saved", {"key": key, "content": content})
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Telemetry side channel: publishing must never break a note save.
|
||||
logger.debug("Failed to publish note_saved event for key '%s': %s", key, exc)
|
||||
|
||||
|
||||
def append_note_content(base_dir: str | Path, key: str, content: str) -> None:
|
||||
@@ -186,7 +197,9 @@ def append_note_content(base_dir: str | Path, key: str, content: str) -> None:
|
||||
last_char_bytes = f.read(1)
|
||||
if last_char_bytes != b"\n":
|
||||
prefix = "\n"
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Cannot inspect the last byte; worst case is a missing
|
||||
# separator newline before the appended content.
|
||||
pass
|
||||
|
||||
with open(file_path, "a", encoding="utf-8") as f:
|
||||
@@ -343,8 +356,10 @@ def update_note_content(
|
||||
if key == "task_plan":
|
||||
try:
|
||||
record_subgoal_hash_chain(base_dir, content, new_content_to_write)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Hash-chain bookkeeping must never break a note edit, but a
|
||||
# missed record can later confuse plan ratchet validation.
|
||||
logger.warning("Failed to record subgoal hash chain for task_plan: %s", exc)
|
||||
return warning
|
||||
|
||||
raise ValueError(
|
||||
@@ -410,8 +425,10 @@ def record_subgoal_hash_chain(
|
||||
if line.strip().startswith("- [ ]"):
|
||||
text = line.strip()[5:].strip()
|
||||
return hashlib.md5(text.encode("utf-8")).hexdigest()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Pure string/hash operations; a failure here is unexpected and
|
||||
# silently disables subgoal rename tracking.
|
||||
logger.warning("Failed to compute active subgoal hash: %s", exc)
|
||||
return None
|
||||
|
||||
old_active = get_active_hash(content_before)
|
||||
@@ -426,8 +443,13 @@ def record_subgoal_hash_chain(
|
||||
if chain_path.exists():
|
||||
try:
|
||||
chain = json.loads(chain_path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
pass
|
||||
except (OSError, ValueError) as exc:
|
||||
logger.warning(
|
||||
"Failed to load subgoal hash chain %s (starting a fresh"
|
||||
" chain; previous rename history is lost): %s",
|
||||
chain_path,
|
||||
exc,
|
||||
)
|
||||
|
||||
# Record mapping: old -> new
|
||||
chain[old_active] = new_active
|
||||
@@ -437,5 +459,12 @@ def record_subgoal_hash_chain(
|
||||
json.dumps(chain, indent=2, ensure_ascii=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except OSError as exc:
|
||||
logger.warning(
|
||||
"Failed to persist subgoal hash chain to %s (rename %s -> %s"
|
||||
" not recorded): %s",
|
||||
chain_path,
|
||||
old_active,
|
||||
new_active,
|
||||
exc,
|
||||
)
|
||||
@@ -703,7 +703,8 @@ def cleanup_video_segments(segments: list[Path], keep_path: Path | None = None)
|
||||
segment.unlink()
|
||||
if segment.parent.exists() and not any(segment.parent.iterdir()):
|
||||
segment.parent.rmdir()
|
||||
except Exception:
|
||||
except OSError:
|
||||
# Best-effort cleanup of temporary segments; leftovers are harmless.
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -600,7 +600,9 @@ def draw_action_overlay_on_image(
|
||||
import ast
|
||||
|
||||
sequence = ast.literal_eval(sequence_str)
|
||||
except Exception:
|
||||
except (ValueError, TypeError, SyntaxError):
|
||||
# Unparseable sequence string: leave it as-is; the
|
||||
# isinstance check below skips non-list values.
|
||||
pass
|
||||
|
||||
if isinstance(sequence, list):
|
||||
|
||||
@@ -27,7 +27,7 @@ def test_extract_error_message():
|
||||
assert _extract_error_message(resp_openai) == "Incorrect API key."
|
||||
|
||||
resp_fallback = MagicMock()
|
||||
resp_fallback.json.side_effect = Exception("JSON parse error")
|
||||
resp_fallback.json.side_effect = ValueError("JSON parse error")
|
||||
resp_fallback.text = "Raw error text"
|
||||
assert _extract_error_message(resp_fallback) == "Raw error text"
|
||||
|
||||
|
||||
Reference in new issue
Block a user