chore: remove unused imports and dead locals (ruff F401, F841) (#17010)

Mechanical cleanup across 43 files — removes 46 unused imports
(F401) and 14 unused local variables (F841) detected by
`ruff check --select F401,F841`. Net: -49 lines.

Also fixes a latent NameError in rl_cli.py where `get_hermes_home()`
was called at module line 32 before its import at line 65 — the
module never imported successfully on main. The ruff audit surfaced
this because it correctly saw the symbol as imported-but-unused
(the call happened before the import ran); the fix moves the import
to the top of the file alongside other stdlib imports.

One `# noqa: F401` kept in hermes_cli/status.py for `subprocess`:
tests monkeypatch `hermes_cli.status.subprocess` as a regression
guard that systemctl isn't called on Termux, so the name must
exist at module scope even though the module body doesn't reference
it. Docstring explains the reason.

Also fixes an invalid `# noqa:` directive in
gateway/platforms/discord.py:308 that lacked a rule code.

Co-authored-by: teknium1 <teknium@users.noreply.github.com>
This commit is contained in:
Teknium 2026-04-28 06:46:45 -07:00 committed by GitHub
parent 3d8be2c617
commit 6085d7a93e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 28 additions and 77 deletions

View File

@ -523,9 +523,6 @@ def _read_claude_code_credentials_from_keychain() -> Optional[Dict[str, Any]]:
Returns dict with {accessToken, refreshToken?, expiresAt?} or None.
"""
import platform
import subprocess
if platform.system() != "Darwin":
return None

View File

@ -7,7 +7,6 @@ import random
import threading
import time
import uuid
import os
import re
from dataclasses import dataclass, fields, replace
from datetime import datetime

View File

@ -47,7 +47,6 @@ from __future__ import annotations
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable, List, Optional

View File

@ -30,7 +30,6 @@ from __future__ import annotations
import json
import logging
import os
import time
import uuid
from types import SimpleNamespace
@ -42,7 +41,6 @@ from agent import google_oauth
from agent.gemini_schema import sanitize_gemini_tool_parameters
from agent.google_code_assist import (
CODE_ASSIST_ENDPOINT,
FREE_TIER_ID,
CodeAssistError,
ProjectContext,
resolve_project_context,

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from typing import Any, Dict, List
from typing import Any, Dict
# Gemini's ``FunctionDeclaration.parameters`` field accepts the ``Schema``
# object, which is only a subset of OpenAPI 3.0 / JSON Schema. Strip fields

View File

@ -29,7 +29,6 @@ from __future__ import annotations
import json
import logging
import os
import time
import urllib.error
import urllib.parse

View File

@ -49,14 +49,13 @@ import json
import logging
import os
import secrets
import socket
import stat
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass, field
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

View File

@ -28,7 +28,6 @@ Usage in run_agent.py:
from __future__ import annotations
import json
import logging
import re
import inspect

View File

@ -8,7 +8,7 @@ streaming, or the _run_codex_stream() call path.
from typing import Any, Dict, List, Optional
from agent.transports.base import ProviderTransport
from agent.transports.types import NormalizedResponse, ToolCall, Usage
from agent.transports.types import NormalizedResponse, ToolCall
class ResponsesApiTransport(ProviderTransport):
@ -151,8 +151,6 @@ class ResponsesApiTransport(ProviderTransport):
"""Normalize Codex Responses API response to NormalizedResponse."""
from agent.codex_responses_adapter import (
_normalize_codex_response,
_extract_responses_message_text,
_extract_responses_reasoning_text,
)
# _normalize_codex_response returns (SimpleNamespace, finish_reason_str)

View File

@ -305,7 +305,7 @@ class VoiceReceiver:
encrypted = bytes(payload_with_nonce[:-4])
try:
import nacl.secret # noqa: delayed import only in voice path
import nacl.secret # noqa: E402 — delayed import, only in voice path
box = nacl.secret.Aead(self._secret_key)
decrypted = box.decrypt(encrypted, header, bytes(nonce))
except Exception as e:

View File

@ -974,7 +974,6 @@ def build_whole_comment_prompt(
def _resolve_model_and_runtime() -> Tuple[str, dict]:
"""Resolve model and provider credentials, same as gateway message handling."""
import os
from gateway.run import _load_gateway_config, _resolve_gateway_model
user_config = _load_gateway_config()

View File

@ -11,10 +11,10 @@ import logging
import re
import time
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Dict
if TYPE_CHECKING:
from gateway.platforms.base import BasePlatformAdapter, MessageEvent
from gateway.platforms.base import MessageEvent
logger = logging.getLogger(__name__)

View File

@ -412,7 +412,6 @@ class MattermostAdapter(BasePlatformAdapter):
import aiohttp
last_exc = None
file_data = None
ct = "application/octet-stream"
fname = url.rsplit("/", 1)[-1].split("?")[0] or f"{kind}.png"

View File

@ -1957,7 +1957,7 @@ class QQAdapter(BasePlatformAdapter):
self, openid: str, content: str, reply_to: Optional[str] = None
) -> SendResult:
"""Send text to a C2C user via REST API."""
msg_seq = self._next_msg_seq(reply_to or openid)
self._next_msg_seq(reply_to or openid)
body = self._build_text_body(content, reply_to)
if reply_to:
body["msg_id"] = reply_to
@ -1970,7 +1970,7 @@ class QQAdapter(BasePlatformAdapter):
self, group_openid: str, content: str, reply_to: Optional[str] = None
) -> SendResult:
"""Send text to a group via REST API."""
msg_seq = self._next_msg_seq(reply_to or group_openid)
self._next_msg_seq(reply_to or group_openid)
body = self._build_text_body(content, reply_to)
if reply_to:
body["msg_id"] = reply_to
@ -2135,11 +2135,6 @@ class QQAdapter(BasePlatformAdapter):
# Route
chat_type = self._guess_chat_type(chat_id)
target_path = (
f"/v2/users/{chat_id}/files"
if chat_type == "c2c"
else f"/v2/groups/{chat_id}/files"
)
if chat_type == "guild":
# Guild channels don't support native media upload in the same way

View File

@ -90,7 +90,7 @@ from gateway.platforms.yuanbao_proto import (
encode_get_group_member_list,
next_seq_no,
)
from gateway.session import SessionSource, build_session_key
from gateway.session import build_session_key
logger = logging.getLogger(__name__)
@ -1897,7 +1897,7 @@ class OwnerCommandMiddleware(InboundMiddleware):
return None, None, False
# Sender identity check: bot owner <-> push.from_account == push.bot_owner_id
owner_id = (push or {}).get("bot_owner_id") or ""
# owner_id = (push or {}).get("bot_owner_id") or ""
# is_owner = bool(owner_id) and owner_id == from_account
is_owner = True
return cmd, cmd_line, is_owner

View File

@ -21,12 +21,10 @@ import hashlib
import hmac
import logging
import os
import re
import secrets
import struct
import time
import urllib.parse
from datetime import datetime, timezone, timedelta
from typing import Optional, Any
import httpx

View File

@ -19,9 +19,8 @@ yuanbao_proto.py - Yuanbao WebSocket 协议编解码(纯 Python 实现)
from __future__ import annotations
import logging
import struct
import threading
from typing import Optional, Union
from typing import Optional
logger = logging.getLogger(__name__)

View File

@ -7486,7 +7486,6 @@ class GatewayRunner:
for m in history
if m.get("role") in ("user", "assistant") and m.get("content")
]
original_count = len(msgs)
approx_tokens = estimate_messages_tokens_rough(msgs)
tmp_agent = AIAgent(

View File

@ -62,7 +62,6 @@ from .config import (
)
from .whatsapp_identity import (
canonical_whatsapp_identifier,
normalize_whatsapp_identifier,
)
from utils import atomic_replace

View File

@ -34,7 +34,7 @@ from dataclasses import dataclass, field
from typing import Optional
from urllib import request as urllib_request
from urllib.error import HTTPError, URLError
from urllib.parse import urlparse, urlunparse
from urllib.parse import urlparse
logger = logging.getLogger(__name__)

View File

@ -562,7 +562,6 @@ def build_welcome_banner(console: Console, model: str, cwd: str,
right_content = "\n".join(right_lines)
layout_table.add_row(left_content, right_content)
agent_name = _skin_branding("agent_name", "Hermes Agent")
title_color = _skin_color("banner_title", "#FFD700")
border_color = _skin_color("banner_border", "#CD7F32")
version_label = format_banner_version_label()

View File

@ -7,7 +7,6 @@ Currently supports:
import io
import json
import os
import sys
import time
import urllib.error

View File

@ -13,7 +13,6 @@ automatically.
from __future__ import annotations
import io
import os
import sys
import time

View File

@ -2953,7 +2953,7 @@ def _setup_sms():
def _setup_dingtalk():
"""Configure DingTalk — QR scan (recommended) or manual credential entry."""
from hermes_cli.setup import (
prompt_choice, prompt_yes_no, print_info, print_success, print_warning,
prompt_choice, prompt_yes_no, print_success, print_warning,
)
dingtalk_platform = next(p for p in _PLATFORMS if p["key"] == "dingtalk")
@ -3504,7 +3504,6 @@ def _setup_qqbot():
method_idx = prompt_choice(" How would you like to set up QQ Bot?", method_choices, 0)
credentials = None
used_qr = False
if method_idx == 0:
# ── QR scan-to-configure ──
@ -3515,8 +3514,6 @@ def _setup_qqbot():
print()
print_warning(" QQ Bot setup cancelled.")
return
if credentials:
used_qr = True
if not credentials:
print_info(" QR setup did not complete. Continuing with manual input.")

View File

@ -19,9 +19,8 @@ format) lives there.
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List
def hooks_command(args) -> None:

View File

@ -4732,7 +4732,6 @@ def _model_flow_anthropic(config, current_model=""):
read_claude_code_credentials,
is_claude_code_token_valid,
_is_oauth_token,
_resolve_claude_code_token_from_credentials,
)
cc_creds = read_claude_code_credentials()
@ -7137,7 +7136,7 @@ def _cmd_update_impl(args, gateway_mode: bool):
print(
f"{svc_name} died after restart, retrying..."
)
retry = subprocess.run(
subprocess.run(
scope_cmd + ["restart", svc_name],
capture_output=True,
text=True,

View File

@ -46,7 +46,6 @@ from __future__ import annotations
import json
import logging
import os
import time
import urllib.error
import urllib.request

View File

@ -999,7 +999,6 @@ def _run_composite_ui(curses, plugin_names, plugin_labels, plugin_selected,
# We need to map logical cursor positions to screen rows
# accounting for non-navigable separator/headers
draw_row = 0 # tracks navigable item index
# --- General Plugins section ---
if n_plugins > 0:

View File

@ -712,8 +712,6 @@ def setup_model_provider(config: dict, *, quick: bool = False):
if isinstance(_m, dict):
selected_provider = _m.get("provider")
nous_subscription_selected = selected_provider == "nous"
# ── Same-provider fallback & rotation setup (full setup only) ──
if not quick and _supports_same_provider_pool_setup(selected_provider):
try:

View File

@ -6,7 +6,7 @@ Shows the status of all Hermes Agent components.
import os
import sys
import subprocess
import subprocess # noqa: F401 — re-exported for tests that monkeypatch status.subprocess to guard against regressions
from pathlib import Path
PROJECT_ROOT = Path(__file__).parent.parent.resolve()

View File

@ -736,7 +736,7 @@ async def get_sessions(limit: int = 20, offset: int = 0):
return {"sessions": sessions, "total": total, "limit": limit, "offset": offset}
finally:
db.close()
except Exception as e:
except Exception:
_log.exception("GET /api/sessions failed")
raise HTTPException(status_code=500, detail="Internal server error")
@ -968,7 +968,7 @@ async def update_config(body: ConfigUpdate):
try:
save_config(_denormalize_config_from_web(body.config))
return {"ok": True}
except Exception as e:
except Exception:
_log.exception("PUT /api/config failed")
raise HTTPException(status_code=500, detail="Internal server error")
@ -997,7 +997,7 @@ async def set_env_var(body: EnvVarUpdate):
try:
save_env_value(body.key, body.value)
return {"ok": True, "key": body.key}
except Exception as e:
except Exception:
_log.exception("PUT /api/env failed")
raise HTTPException(status_code=500, detail="Internal server error")
@ -1011,7 +1011,7 @@ async def remove_env_var(body: EnvVarDelete):
return {"ok": True, "key": body.key}
except HTTPException:
raise
except Exception as e:
except Exception:
_log.exception("DELETE /api/env failed")
raise HTTPException(status_code=500, detail="Internal server error")
@ -1568,7 +1568,6 @@ async def _start_device_code_flow(provider_id: str) -> Dict[str, Any]:
then spawns a background poller. Returns the user-facing display fields
so the UI can render the verification page link + user code.
"""
from hermes_cli import auth as hauth
if provider_id == "nous":
from hermes_cli.auth import _request_device_code, PROVIDER_REGISTRY
import httpx

View File

@ -11,7 +11,6 @@ hot-reloaded by the webhook adapter without a gateway restart.
"""
import json
import os
import re
import secrets
import time

View File

@ -27,6 +27,8 @@ from pathlib import Path
import fire
import yaml
from hermes_constants import OPENROUTER_BASE_URL, get_hermes_home
# Load .env from ~/.hermes/.env first, then project root as dev fallback.
# User-managed env files should override stale shell exports on restart.
_hermes_home = get_hermes_home()
@ -60,8 +62,6 @@ from tools.rl_training_tool import get_missing_keys
# Config Loading
# ============================================================================
from hermes_constants import get_hermes_home, OPENROUTER_BASE_URL
DEFAULT_MODEL = "anthropic/claude-opus-4.5"
DEFAULT_BASE_URL = OPENROUTER_BASE_URL
@ -412,7 +412,7 @@ def main(
# Run the agent
print("\n" + "=" * 60)
response = agent.run_conversation(user_input)
agent.run_conversation(user_input)
print("\n" + "=" * 60)
except KeyboardInterrupt:
@ -429,7 +429,7 @@ def main(
print("-" * 40)
try:
response = agent.run_conversation(task)
agent.run_conversation(task)
print("\n" + "=" * 60)
print("✅ Task completed")
except KeyboardInterrupt:

View File

@ -2720,7 +2720,6 @@ class AIAgent:
eff_api_mode = api_mode if api_mode is not None else (self.api_mode or "")
eff_model = (model if model is not None else self.model) or ""
base_lower = eff_base_url.lower()
model_lower = eff_model.lower()
provider_lower = eff_provider.lower()
is_claude = "claude" in model_lower

View File

@ -20,7 +20,6 @@ from __future__ import annotations
import asyncio
import json
import logging
import os
from typing import Any, Dict, Optional
from tools.registry import registry, tool_error

View File

@ -25,7 +25,7 @@ import json
import logging
import threading
import time
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple
import websockets

View File

@ -526,7 +526,6 @@ def _url_is_private(url: str) -> bool:
backend is configured, which will surface the DNS error naturally).
"""
try:
from tools.url_safety import is_safe_url
# is_safe_url returns False for private/loopback/link-local/CGNAT AND
# for DNS failures. We only want the private-network case here, so
# we parse + check the host shape as a DNS-failure sieve first.

View File

@ -27,7 +27,6 @@ import time
from concurrent.futures import (
ThreadPoolExecutor,
TimeoutError as FuturesTimeoutError,
as_completed,
)
from typing import Any, Dict, List, Optional

View File

@ -32,7 +32,6 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
from pathlib import Path
from hermes_constants import get_hermes_home
from tools.binary_extensions import BINARY_EXTENSIONS
from agent.file_safety import (

View File

@ -7,7 +7,6 @@ import logging
import os
import threading
from pathlib import Path
from typing import Optional
from agent.file_safety import get_read_block_error
from tools.binary_extensions import has_binary_extension

View File

@ -836,7 +836,6 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A
return _transcribe_mistral(file_path, model_name)
if provider == "xai":
xai_cfg = stt_config.get("xai", {})
# xAI Grok STT doesn't use a model parameter — pass through for logging
model_name = model or "grok-stt"
return _transcribe_xai(file_path, model_name)

View File

@ -20,7 +20,7 @@ from __future__ import annotations
import logging
from pathlib import Path
from typing import TYPE_CHECKING, List, Optional, Tuple
from typing import List, Optional, Tuple
logger = logging.getLogger(__name__)
@ -34,10 +34,6 @@ def _get_active_adapter():
return None
if TYPE_CHECKING:
from gateway.platforms.yuanbao import YuanbaoAdapter
# ---------------------------------------------------------------------------
# 角色标签
# ---------------------------------------------------------------------------
@ -418,7 +414,7 @@ async def send_dm(
# Registry registration
# ---------------------------------------------------------------------------
from tools.registry import registry, tool_result, tool_error # noqa: E402
from tools.registry import registry, tool_result # noqa: E402
def _check_yuanbao():

View File

@ -37,7 +37,7 @@ import yaml
import logging
import asyncio
from pathlib import Path
from typing import List, Dict, Any, Optional, Tuple, Callable
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime