Skip to content

Batch salvage group 3: 9 low-risk new-contributor PRs (custom-providers/non-vision-strip/telegram-dm-topic/cron-isolation/etc)#27302

Merged
teknium1 merged 10 commits into
mainfrom
hermes/hermes-e1ed3e9f
May 17, 2026
Merged

Batch salvage group 3: 9 low-risk new-contributor PRs (custom-providers/non-vision-strip/telegram-dm-topic/cron-isolation/etc)#27302
teknium1 merged 10 commits into
mainfrom
hermes/hermes-e1ed3e9f

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Batch salvage group 3 — 9 low-risk new-contributor PRs onto current main with per-commit contributor authorship preserved. Continues the LHF run from #27247 (group 1) and #27292 (group 2).

Salvaged PRs

PR Author Change
#26766 @darvsum fix: preserve discover_models in _normalize_custom_provider_entry so unreachable endpoints stop blocking /api/model/options
#26498 @hueilau fix: strip image parts for non-vision models on the provider-profile build path (DeepSeek etc. stop 400-ing on image_url)
#27114 @Timur00Kh fix(gateway): populate direct_messages_topic_id for queued/synthetic Telegram DM events so goal continuations land in the right topic
#27061 @Grogger fix(windows): guard subprocess spawns with CREATE_NO_WINDOW so CMD windows stop flashing
#27042 @lemassykoi fix(model-switch): probe /models for keyless local providers (llama.cpp/Ollama) so the gateway picker matches CLI behavior
#26707 @draplater feat: inject current time into the /goal judge prompt so time-bounded goals ("until 5pm") can actually be evaluated
#27048 @pr7426 fix(cron): switch parallel cron result collection to as_completed + per-future try/except so one job failure doesn't drop the rest
#26215 @therahul-yo fix(tests): mock keychain in TestReadClaudeCodeCredentials to prevent real macOS Keychain credential leak into test output
#27205 @flamiinngo fix(scripts): reconfigure stdout/stderr to UTF-8 so the footgun checker's ✓/✗ glyphs stop UnicodeEncodeError-ing on Windows cp1252

Pre-flight drop

#27154 (0xchainer — missing logger NameError) dropped from this batch — already on main as 4e9cedc. The fix (logger import + module-level instance) and the smoke test (test_module_has_logger) both landed under their proper @0xchainer noreply identity before this batch ran. Will close as redundant.

Attribution

Test plan

  • Compile-clean across all 12 touched .py files.
  • Targeted suites: tests/agent/test_anthropic_adapter.py + tests/hermes_cli/test_goals.py + tests/cron/ → 547 passed in 11.76s.

Rebase-merge so per-commit contributor authorship survives.

darvsum and others added 10 commits May 16, 2026 23:02
The _normalize_custom_provider_entry() function was dropping the
discover_models field from custom_provider entries because:

1. It was not listed in _KNOWN_KEYS, so it was logged as an
   unknown key and ignored.
2. The function builds the normalized dict by explicitly copying
   known fields, so even if the warning was suppressed, the value
   was not carried through.

This caused downstream model_switch.py to default discover_models
to True, triggering /models HTTP probes on unreachable endpoints.
With 4 unreachable internal endpoints at ~6s timeout each, the
/api/model/options endpoint took ~24s instead of <1s.
_propare_messages_for_non_vision_model() was only called in the legacy
flag path (no provider profile). Providers with registered profiles
(e.g. DeepSeek, Kimi) bypassed the strip, causing HTTP 400 errors when
image_url content blocks reached their non-vision APIs.

This mirrors the existing behavior in the legacy path, ensuring all
non-vision models get image stripping regardless of profile status.
Vision-capable models are unaffected (the function is a no-op for them).
…events

When /goal loop generates synthetic MessageEvents (goal continuations,
status notices), the reply anchor is unavailable (message_id=None). For
Telegram DM topic lanes, the Telegram adapter requires
direct_messages_topic_id to route messages correctly; without it, the
adapter falls back to message_thread_id=None, sending messages to the
root 'All Messages' thread instead of the active topic lane.

The fix includes direct_messages_topic_id in thread metadata for all
non-General Telegram DM topics, ensuring queued/synthetic messages are
delivered to the correct thread even when no reply anchor exists.
Add creationflags=CREATE_NO_WINDOW to every Windows Popen call
across the terminal, process registry, code execution, and kanban
worker subsystems. Prevents visible CMD windows from flashing on
the user's desktop during agent operation.

Also adds the _IS_WINDOWS module constant to kanban_db.py where
it was missing, for consistency with the other patched files.

5 Popen sites across 4 files:
- tools/environments/local.py (terminal foreground spawn)
- tools/process_registry.py (background process spawn)
- tools/code_execution_tool.py (sandbox + interpreter probe)
- hermes_cli/kanban_db.py (kanban worker spawn)
The Telegram/Discord model picker skipped live model discovery for
custom providers (llama.cpp, Ollama) unless an api_key was configured.
Local providers typically don't require auth on the /models endpoint.

The CLI always probes /models, so this brings the gateway picker into
parity.

Change: `if api_url and api_key:` -> `if api_url:`
The goal judge only receives the goal text and the agent's last
response. It has no concept of the current time, making it
impossible to evaluate time-sensitive goals like 'keep working
until 5pm'.

This commit adds 'Current time' to both JUDGE_USER_PROMPT_TEMPLATE
and JUDGE_USER_PROMPT_WITH_SUBGOALS_TEMPLATE, computed from
datetime.now().astimezone() at judge call time.
Replace generator-based result collection with explicit per-future
handling. Each future is now processed independently with a 600s timeout.

Before: _results.extend(f.result() for f in _futures)
- One exception stops the generator, remaining results are lost
- No timeout: one hung job blocks the entire tick

After: as_completed() + per-future try/except
- Each future handled independently
- 600s timeout prevents indefinite blocking
- Failed futures are logged and counted as failures
… credential leakage

Tests in TestReadClaudeCodeCredentials were not mocking
_read_claude_code_credentials_from_keychain, which was added after the
tests were written. On macOS machines with real Claude Code credentials
stored in the Keychain, the function returns live credentials instead of
the test fixtures, causing assertions to fail and leaking real tokens in
test output.

Add an autouse fixture that stubs the keychain reader to None so all
tests in the class exercise only the file-based credential path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The check-windows-footguns.py script outputs a checkmark (U+2713) and
cross (U+2717) to report results. Windows terminals default to cp1252,
which cannot encode these characters, so running the script on Windows
threw a UnicodeEncodeError before any results were printed.

This made the tool completely unusable on the exact platform it exists
to help -- a developer on Windows trying to check their code for
Windows-safety issues would just get a crash instead.

Fix: reconfigure stdout and stderr to UTF-8 at the start of main(),
before any output is produced. Verified on Windows 11 Home with
Python 3.13 (terminal defaulting to cp1252).
…tors

Adds release-note attribution mappings for 9 contributors from group 3:
- @darvsum (PR #26766)
- @hueilau (PR #26498)
- @Timur00Kh (PR #27114)
- @Grogger (PR #27061)
- @lemassykoi (PR #27042)
- @draplater (PR #26707)
- @pr7426 (PR #27048)
- @therahul-yo (PR #26215)
- @flamiinngo (PR #27205)

#27154 dropped from this batch — already landed on main as 4e9cedc.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-e1ed3e9f vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8356 on HEAD, 0 on base (🆕 +8356)

🆕 New issues (4370):

Rule Count
unresolved-import 1343
invalid-argument-type 1028
unresolved-attribute 951
invalid-assignment 452
unsupported-operator 143
invalid-parameter-default 118
not-subscriptable 87
invalid-method-override 86
invalid-return-type 38
no-matching-overload 31
call-non-callable 29
unresolved-reference 18
unused-type-ignore-comment 16
invalid-type-form 14
not-iterable 6
+6 more rules
First entries
hermes_state.py:396: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `Connection`, found `Connection | None`
tests/gateway/test_reply_to_injection.py:10: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_fuzzy_match.py:29: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["identical"]` and `str | None`
agent/curator_backup.py:203: [invalid-assignment] invalid-assignment: Cannot assign to a subscript on an object of type `int`
optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py:2669: [invalid-argument-type] invalid-argument-type: Argument to bound method `Migrator.record` is incorrect: Expected `Path | None`, found `Literal["openclaw.json browser.*"]`
cli.py:6947: [unsupported-operator] unsupported-operator: Operator `>=` is not supported between objects of type `str | list[Unknown] | int | Unknown | None` and `int`
tests/hermes_cli/test_kanban_db.py:318: [unresolved-attribute] unresolved-attribute: Attribute `id` is not defined on `None` in union `Run | None`
tests/run_agent/test_compression_boundary_hook.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tui_gateway/server.py:4030: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `None` in union `Unknown | None | dict[Unknown, Unknown]`
tests/run_agent/test_streaming.py:509: [unresolved-attribute] unresolved-attribute: Object of type `AIAgent` has no attribute `_disable_streaming`
tests/tools/test_skill_manager_tool.py:179: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["'references/myfile.md'"]` and `str | None`
tests/gateway/test_matrix.py:8: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
gateway/platforms/yuanbao.py:2621: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `MessageType`, found `Literal[MessageType.DOCUMENT] | Any | None`
tests/tools/test_session_search.py:441: [invalid-argument-type] invalid-argument-type: Argument to function `session_search` is incorrect: Expected `int`, found `None`
tests/gateway/test_discord_channel_skills.py:3: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_ssh_environment.py:8: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_simplex_plugin.py:14: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
agent/lsp/cli.py:142: [no-matching-overload] no-matching-overload: No overload of bound method `str.join` matches arguments
tests/hermes_cli/test_kanban_core_functionality.py:1778: [unresolved-attribute] unresolved-attribute: Attribute `summary` is not defined on `None` in union `Run | None`
tests/cli/test_cli_user_message_preview.py:49: [invalid-assignment] invalid-assignment: Object of type `ModuleType` is not assignable to `<module 'cli'>`
tests/gateway/test_platform_base.py:426: [invalid-method-override] invalid-method-override: Invalid override of method `get_chat_info`: Definition is incompatible with `BasePlatformAdapter.get_chat_info`
tests/hermes_cli/test_pin_kanban_board_env.py:13: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
agent/codex_responses_adapter.py:974: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["summary"]` and value of type `list[Unknown]` on object of type `dict[str, str]`
cli.py:8191: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `list[str]`, found `list[str] | set[str]`
tests/gateway/test_media_download_retry.py:841: [invalid-assignment] invalid-assignment: Object of type `AsyncMock` is not assignable to attribute `send` of type `def send(self, chat_id: str, content: str, reply_to: str | None = None, metadata: dict[str, Any] | None = None) -> CoroutineType[Any, Any, SendResult]`
... and 4345 more

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/cron Cron scheduler and job management labels May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.