Skip to content

fix: systematic ty type-checker cleanup across codebase#13481

Open
alt-glitch wants to merge 11 commits intomainfrom
sid/types-and-lints
Open

fix: systematic ty type-checker cleanup across codebase#13481
alt-glitch wants to merge 11 commits intomainfrom
sid/types-and-lints

Conversation

@alt-glitch
Copy link
Copy Markdown
Collaborator

@alt-glitch alt-glitch commented Apr 21, 2026

Summary

Systematic pass through the codebase fixing diagnostics reported by ty (Astral's type checker), organized by error category. Eliminates hundreds of type errors across ~82 files, cleans up redundant imports, declares missing optional dependencies, removes silent failure patterns, and — most recently — lands a focused P1 hotfix batch for actual runtime-crash bugs surfaced by triaging the remaining diagnostic buckets.

What changed

Type fixes by category

  • unresolved-reference — Added TYPE_CHECKING imports for forward references (gemini adapters, copilot client, etc.); defined missing _CamofoxConfig TypedDict referenced by _BrowserConfig.
  • unresolved-import — Corrected skills_hub imports in tui_gateway/server.py (was pointing at the wrapper module, now targets the source). Added _setup_wecom_callback() function that hermes_cli/setup.py was importing.
  • unresolved-attributeRunState dataclass gained missing api_log_file / trainer_log_file / env_log_file fields (stop-run flow was closing undeclared handles); added CredentialPool.remove_entry(entry_id) method (symmetric with add_entry, called during OAuth dashboard cleanup); os.sys.modulessys.modules typo fix; None-guards before msg.sticker.* and re.search(...).group().
  • invalid-return-type — Widened return annotations to match actual control flow, added unreachable assertions after retry loops ty can't prove terminate, split read_credential_pool into two functions to eliminate ambiguous union returns.
  • call-non-callable — Fixed parameter shadowing imported callable (batch_runner.py), narrowed Optional before calling, typed callback dicts properly.
  • not-subscriptable / invalid-key — Added TypedDict for DEFAULT_CONFIG (and nested configs), CLI state dicts (_ModelPickerState, _ApprovalState, _ClarifyState), coerced str | None before slicing. None-guards + variant narrowing before interaction.data["values"] access in Discord select handlers.
  • unsupported-operator — None-guards before arithmetic / membership / comparison on Optional values: Discord thread_id + guild.member_count, display spinner start_time, gateway _agent_timeout.
  • invalid-method-override — Email platform's send_image accepts metadata kwarg to match base class; send_document accepts **kwargs (was dropping metadata entirely → TypeError for callers passing metadata=...).
  • invalid-type-form — Replaced builtin callable / any in annotation position with Callable[..., Any] / Any across 20 sites in run_agent.py, cli.py, tools/delegate_tool.py, toolset_distributions.py, hermes_cli/dingtalk_auth.py, tui_gateway/server.py.
  • invalid-assignmentrun_conversation.conversation_history param widened to list[dict[str, Any]] | None so the 5 None-reset sites type-check.
  • invalid-argument-typebuild_anthropic_client(timeout) widened to Optional[float] (body already handled None); OMIT_TEMPERATURE sentinel no longer leaks into call_llm(temperature: float) — conditional-kwargs-dict pattern in run_agent.py and scripts/trajectory_compressor.py.
  • deprecateddatetime.utcnow()datetime.now(timezone.utc) in gateway/platforms/bluebubbles.py (Python 3.12+ deprecation).

Dependency hygiene

  • Declared undeclared soft deps in pyproject.toml extras: mutagen and aiohttp-socks[messaging], tiktoken and Pillow[cli], psutil[mcp], datasets[rl], new [tts-local] extra for neutts + soundfile.
  • Removed silent import guards — Previously these packages were behind try/except ImportError with silent fallbacks (returning empty dicts, skipping features, estimating values from file sizes). Now they crash loudly if the correct extra isn't installed, instead of silently degrading.

Code cleanup

  • Removed redundant local imports already available at module scope (comprehensive sweep across gateway adapters, tools, CLI modules).
  • Moved standalone scripts (batch_runner.py, mini_swe_runner.py, rl_cli.py, trajectory_compressor.py) to scripts/ directory with updated imports and test references.
  • Removed AIOHTTP_AVAILABLE guard pattern from api_server.py — aiohttp is now a direct import since it's a required dep of the messaging extra.

P1 runtime-bug hotfix batch

Landed as 15ac253b + 67bc4410 (simplify follow-up). Triaged the remaining ty diagnostics (1953 across 16 rules) into genuine runtime crashes vs false positives vs annotation gaps. Fixed the 15 P1 ship-stoppers that would fire AttributeError / TypeError on the happy path:

  • Missing refsCredentialPool.remove_entry, RunState log-file fields, _CamofoxConfig TypedDict, _setup_wecom_callback(), tui_gateway/server.py skills_hub path.
  • None-guards — Telegram (sticker handler), Discord (interaction.data + SelectMenu narrowing, thread_id, member_count), Matrix (walrus-narrow re.search), agent/display.py (start_time), gateway/run.py (_agent_timeout).
  • Platform override signature match — email send_image / send_document.
  • API-shape fixesbuild_anthropic_client(timeout: Optional[float]), conversation_history | None, OMIT_TEMPERATURE sentinel guard.
  • Deprecationdatetime.utcnow().

Net P1-batch impact: -138 ty diagnostics (1953 → 1815), with wins on not-subscriptable (-34), unresolved-attribute (-29), invalid-argument-type (-26), invalid-type-form (-20), unsupported-operator (-18), invalid-key (-9).

Tests

  • tests/agent/test_credential_pool.pyCredentialPool.remove_entry coverage (id match, unknown-id return, priority renumbering).
  • tests/hermes_cli/test_config_shapes.py_CamofoxConfig / _BrowserConfig TypedDict shape smoke tests.
  • tests/tools/test_rl_training_tool.pyRunState log-file field defaults.

Test plan

  • ty check --python-version 3.13 passes for all fixed categories
  • uv sync --extra all installs newly declared deps
  • Existing test suite passes (pytest tests/)
  • Gateway platforms start correctly with messaging extra installed
  • CLI tools (clipboard paste, vision resize, token estimation) work with cli extra
  • New P1 structural tests pass (pytest tests/agent/test_credential_pool.py tests/hermes_cli/test_config_shapes.py 'tests/tools/test_rl_training_tool.py::TestRunStateLogFileFields')

…level

Sweep ~74 redundant local imports across 21 files where the same module
was already imported at the top level. Also includes type fixes and lint
cleanups on the same branch.
Full AST-based scan of all .py files to find every case where a module
or name is imported locally inside a function body but is already
available at module level.  This is the second pass — the first commit
handled the known cases from the lint report; this one catches
everything else.

Files changed (19):

  cli.py                — 16 removals: time as _time/_t/_tmod (×10),
                           re / re as _re (×2), os as _os, sys,
                           partial os from combo import,
                           from model_tools import get_tool_definitions
  gateway/run.py        —  8 removals: MessageEvent as _ME /
                           MessageType as _MT (×3), os as _os2,
                           MessageEvent+MessageType (×2), Platform,
                           BasePlatformAdapter as _BaseAdapter
  run_agent.py          —  6 removals: get_hermes_home as _ghh,
                           partial (contextlib, os as _os),
                           cleanup_vm, cleanup_browser,
                           set_interrupt as _sif (×2),
                           partial get_toolset_for_tool
  hermes_cli/main.py    —  4 removals: get_hermes_home, time as _time,
                           logging as _log, shutil
  hermes_cli/config.py  —  1 removal:  get_hermes_home as _ghome
  hermes_cli/runtime_provider.py
                        —  1 removal:  load_config as _load_bedrock_config
  hermes_cli/setup.py   —  2 removals: importlib.util (×2)
  hermes_cli/nous_subscription.py
                        —  1 removal:  from hermes_cli.config import load_config
  hermes_cli/tools_config.py
                        —  1 removal:  from hermes_cli.config import load_config, save_config
  cron/scheduler.py     —  3 removals: concurrent.futures, json as _json,
                           from hermes_cli.config import load_config
  batch_runner.py       —  1 removal:  list_distributions as get_all_dists
                           (kept print_distribution_info, not at top level)
  tools/send_message_tool.py
                        —  2 removals: import os (×2)
  tools/skills_tool.py  —  1 removal:  logging as _logging
  tools/browser_camofox.py
                        —  1 removal:  from hermes_cli.config import load_config
  tools/image_generation_tool.py
                        —  1 removal:  import fal_client
  environments/tool_context.py
                        —  1 removal:  concurrent.futures
  gateway/platforms/bluebubbles.py
                        —  1 removal:  httpx as _httpx
  gateway/platforms/whatsapp.py
                        —  1 removal:  import asyncio
  tui_gateway/server.py —  2 removals: from datetime import datetime,
                           import time

All alias references (_time, _t, _tmod, _re, _os, _os2, _json, _ghh,
_ghome, _sif, _ME, _MT, _BaseAdapter, _load_bedrock_config, _httpx,
_logging, _log, get_all_dists) updated to use the top-level names.
Widen return type annotations to match actual control flow, add
unreachable assertions after retry loops ty cannot prove terminate,
split ambiguous union returns (auth.py credential pool), and remove
the AIOHTTP_AVAILABLE conditional-import guard from api_server.py.
Move batch_runner, trajectory_compressor, mini_swe_runner, and rl_cli
from the project root into scripts/, update all imports, logger names,
pyproject.toml, and downstream test references.
Replace hasattr() duck-typing with isinstance() checks for DiscordAdapter
in gateway/run.py, add TypedDict for IMAGEGEN_BACKENDS in tools_config.py,
properly type fal_client getattr'd callables in image_generation_tool.py,
fix dict[str, object] → Callable annotation in approval.py, use
isinstance(BaseModel) in web_tools.py, capture _message_handler to local
in base.py, rename shadowed list_distributions parameter in batch_runner.py,
and remove dead queue_message branch.
…guards

Previously mutagen, aiohttp-socks, tiktoken, Pillow, psutil, datasets,
neutts, and soundfile were used behind try/except ImportError with silent
fallbacks, masking broken functionality at runtime.  Declare each in its
natural extra (messaging, cli, mcp, rl, new tts-local) so they get
installed, and remove the guards so missing deps crash loudly.
Add TypedDicts for DEFAULT_CONFIG, CLI state dicts (_ModelPickerState,
_ApprovalState, _ClarifyState), and OPTIONAL_ENV_VARS so ty can resolve
nested dict subscripts.  Guard Optional returns before subscripting
(toolsets, cron/scheduler, delegate_tool), coerce str|None to str before
slicing (gateway/run, run_agent), split ternary for isinstance narrowing
(wecom), and suppress discord interaction.data access with ty: ignore.
Comment thread run_agent.py Dismissed
15 P1 ship-stopper runtime bugs from the ty triage plus the cross-bucket
cleanup in run_agent.py. Net: -138 ty diagnostics (1953 -> 1815). Major
wins on not-subscriptable (-34), unresolved-attribute (-29),
invalid-argument-type (-26), invalid-type-form (-20),
unsupported-operator
(-18), invalid-key (-9).

Missing refs (structural):
- tools/rl_training_tool.py: RunState dataclass gains api_log_file,
  trainer_log_file, env_log_file fields; stop-run was closing undeclared
  handles.
- agent/credential_pool.py: remove_entry(entry_id) added, symmetric with
  add_entry; used by hermes_cli/web_server.py OAuth dashboard cleanup.
- hermes_cli/config.py: _CamofoxConfig TypedDict defined (was referenced
  by _BrowserConfig but never declared).
- hermes_cli/gateway.py: _setup_wecom_callback() added, mirroring
  _setup_wecom().
- tui_gateway/server.py: skills_hub imports corrected from
  hermes_cli.skills_hub -> tools.skills_hub.

Typo / deprecation:
- tools/transcription_tools.py: os.sys.modules -> sys.modules.
- gateway/platforms/bluebubbles.py: datetime.utcnow() ->
  datetime.now(timezone.utc).

None-guards:
- gateway/platforms/telegram.py:~2798 - msg.sticker None guard.
- gateway/platforms/discord.py:3602/3637 - interaction.data None +
  SelectMenu narrowing; :3009 - thread_id None before `in`; :1893 -
  guild.member_count None.
- gateway/platforms/matrix.py:2174/2185 - walrus-narrow
  re.search().group().
- agent/display.py:732 - start_time None before elapsed subtraction.
- gateway/run.py:10334 - assert _agent_timeout is not None before `//
  60`.

Platform override signature match:
- gateway/platforms/email.py: send_image accepts metadata kwarg;
  send_document accepts **kwargs (matches base class).

run_agent.py annotation pass:
- callable/any -> Callable/Any in annotation position (15 sites in
  run_agent.py + 5 in cli.py, toolset_distributions.py,
  tools/delegate_tool.py, hermes_cli/dingtalk_auth.py,
  tui_gateway/server.py).
- conversation_history param widened to list[dict[str, Any]] | None.
- OMIT_TEMPERATURE sentinel guarded from leaking into
  call_llm(temperature): kwargs-dict pattern at run_agent.py:7337 +
  scripts/trajectory_compressor.py:618/688.
- build_anthropic_client(timeout) widened to Optional[float].

Tests:
- tests/agent/test_credential_pool.py: remove_entry (id match,
  unknown-id, priority renumbering).
- tests/hermes_cli/test_config_shapes.py: _CamofoxConfig shape +
  nesting.
- tests/tools/test_rl_training_tool.py: RunState log_file fields.
# Conflicts:
#	gateway/platforms/base.py
#	gateway/platforms/qqbot/adapter.py
#	gateway/platforms/slack.py
#	hermes_cli/main.py
#	scripts/batch_runner.py
#	tools/skills_tool.py
#	uv.lock
Follow-up to 15ac253 per /simplify review:

- gateway/platforms/discord.py:3638 - move self.resolved = True *after*
  the `if interaction.data is None: return` guard. Previously the view
  was marked resolved before the None-guard, so a None data payload
  silently rejected the user's next click.
- agent/display.py:732 - replace `if self.start_time is None: continue`
  with `assert self.start_time is not None`. start() sets start_time
  before the animate thread starts, so the None branch was dead; the
  `continue` form would have busy-looped (skipping the 0.12s sleep).
- tests/hermes_cli/test_config_shapes.py - drop __total__ dunder
  restatement test (it just echoes the class declaration); trim commit
  narration from module docstring.
- tests/agent/test_credential_pool.py, tests/tools/test_rl_training_tool.py -
  drop "added in commit ..." banners (narrates the change per CLAUDE.md).
@alt-glitch alt-glitch added the type/refactor Code restructuring, no behavior change label Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants