chore: sync with upstream main (2026-05-19)#37
Conversation
The WeCom adapter's _read_events() loop only handled CLOSE, CLOSED,
and ERROR websocket message types. When the server initiates a graceful
shutdown, aiohttp returns WSMsgType.CLOSING before the connection is
fully closed. This message type was not handled, causing the receive()
call to return immediately in a tight loop while self._ws.closed
remained False. The result was 100% CPU usage on the asyncio event loop.
Add WSMsgType.CLOSING to the set of terminal message types that raise
RuntimeError("WeCom websocket closed"), allowing _listen_loop() to
enter its normal reconnect backoff path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes NousResearch#28140 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gateway.log uses a _ComponentFilter that only passes records from
loggers starting with ('gateway',). Plugin modules are loaded under
the hermes_plugins.* namespace, so all plugin log output is silently
dropped from gateway.log.
This makes plugin registration — which directly affects gateway hooks
(pre_gateway_dispatch, transform_llm_output, etc.) — invisible in
the gateway-specific log. Operators debugging gateway behavior check
gateway.log and see no plugin activity, even when plugins are working
correctly.
Add 'hermes_plugins' to the gateway component prefixes tuple so
plugin log messages appear in gateway.log.
Closes NousResearch#28138
_deliver_kanban_artifacts used a broader _IMAGE_EXTS that included .bmp, .tiff, and .svg. These three extensions are absent from the equivalent set in _deliver_media_from_response (line 10661), which intentionally routes them through send_document rather than send_multiple_images (comment near line 10522 notes that Telegram sendPhoto recompresses and rejects non-raster formats). Routing .svg (XML text), .bmp, or .tiff through the photo API causes send_multiple_images to raise on most platforms; the exception is caught and logged as a warning, silently dropping the artifact. Aligning the two sets ensures kanban deliverables with these extensions follow the same send_document path as regular agent responses. No behaviour change for .png/.jpg/.jpeg/.gif/.webp.
…revent keyboard freeze Background process non-PTY path used stdin=subprocess.PIPE unconditionally, creating an orphan pipe that was never written to and never closed. Child processes that read stdin would block indefinitely, competing with the parent's prompt_toolkit event loop for terminal ownership and causing complete keyboard lockout. Change to stdin=subprocess.DEVNULL so children get immediate EOF on stdin reads instead of blocking forever. For interactive stdin, the PTY path (which has its own independent PTY via ptyprocess.PtyProcess.spawn) should be used instead. Fixes NousResearch#17959
…earch#28317) * fix(process-registry): detach stdin from background subprocesses to prevent keyboard freeze Background process non-PTY path used stdin=subprocess.PIPE unconditionally, creating an orphan pipe that was never written to and never closed. Child processes that read stdin would block indefinitely, competing with the parent's prompt_toolkit event loop for terminal ownership and causing complete keyboard lockout. Change to stdin=subprocess.DEVNULL so children get immediate EOF on stdin reads instead of blocking forever. For interactive stdin, the PTY path (which has its own independent PTY via ptyprocess.PtyProcess.spawn) should be used instead. Fixes NousResearch#17959 * chore(release): alias stale-ID salvage commit for LifeJiggy PR NousResearch#28315 was salvaged with a wrong noreply numeric ID (192385615 vs the correct 141562589). The commit on main is correctly authored to LifeJiggy by username, but the noreply email doesn't match AUTHOR_MAP. Adds an alias so release-notes generation maps both forms to the same contributor. --------- Co-authored-by: LifeJiggy <192385615+LifeJiggy@users.noreply.github.com>
Plugin discovery exceptions in gateway startup (gateway/run.py) and CLI startup (hermes_cli/main.py) are caught and logged at DEBUG level, making them invisible at the default INFO log level. If any plugin import fails — syntax error, missing dependency, import cycle — operators get zero indication unless they bump the log level to DEBUG. This makes broken plugins appear enabled but silently non-functional. Change both locations to logger.warning() so failures are visible at production log levels. Closes NousResearch#28137
… macOS
Path.resolve() follows the /tmp -> /private/tmp symlink on macOS, so
str(path).startswith("/tmp/") is always False for temp-dir paths.
The "Accept Edits" (workspace_session) mode silently refused to
auto-approve every /tmp write on macOS, breaking the documented
behaviour and making the existing test fail on this platform.
Fix: keep the raw expanded path (pre-resolve) for the /tmp prefix
check and continue using the resolved form only for the cwd
relative_to() call where symlink resolution is correct behaviour.
Switch .hermes-kanban-columns from auto-fit CSS grid to a flex row with overflow-x: auto and a hidden scrollbar (scrollbar-width / ::-webkit- scrollbar), and pin .hermes-kanban-column to flex: 0 0 280px so columns sit side-by-side at a fixed width instead of wrapping into a 2xN grid. Page vertical scroll is unaffected: each column already caps at max-height: calc(100vh - 220px), so the container never grows tall enough to introduce its own vertical scrollbar.
…sible to LLM When a tool call requires user approval in the non-blocking gateway path, the LLM previously received a result that was indistinguishable from a failed tool call (exit_code=-1, error=message). The LLM could not tell whether the tool was pending approval, had returned empty results, or had failed silently — causing it to burn context on wrong hypotheses. Fix changes the result format to include: - status: pending_approval (clear state name) - approval_pending: True (explicit boolean for LLMs to detect) - error: cleared to empty string (removes misleading error signal) This lets the LLM reason about approval latency vs actual errors, short-circuiting the previous silent failure mode. Fixes NousResearch#14806
GLM models via Ollama report finish_reason='stop' even when the response was truncated by max_tokens. The continuation mechanism uses _has_natural_response_ending() as one of the heuristics to detect whether the response was genuinely finished. Currently only ASCII punctuation and CJK punctuation are recognized. This means any response ending with an emoji (e.g. ⚡, 👍) or the caret character ^ (common in French ^^ smiley) is not recognized as naturally ended, triggering a false-positive continuation where the model receives 'Continue where you left off' and produces garbled output. Add: - ^ (caret) to the punctuation set - Unicode emoji range (codepoint >= 0x1F300) as natural ending This only affects GLM/Ollama users but the fix is safe for all backends since _has_natural_response_ending() is only consulted inside the continuation flow.
…ousResearch#28328) Pre-stages AUTHOR_MAP entries for 10 new contributors whose PRs are being salvaged in the May 2026 low-hanging-fruit batch (group 8). Lands ahead of the per-PR salvage PRs so they don't get blocked by AUTHOR_MAP CI. Contributors: - AceWattGit (NousResearch#28159 — _pool_may_recover_from_rate_limit NameError) - YuanHanzhong (NousResearch#28032 — x.com/status fallbacks link-like) - colin-chang (NousResearch#28245, NousResearch#28249, NousResearch#28251 — gateway + mattermost fixes) - felix-windsor (NousResearch#28019 — preserve cron asterisks in strip mode) - houenyang-momo (NousResearch#28205 — charizard completion menu contrast) - iqdoctor (NousResearch#28095 — windows installer docs) - joe102084 (NousResearch#28151 — whitespace-only cron responses) - jvinals (NousResearch#27936 — Slack U-IDs → DM channel) - maxmilian (NousResearch#28267 — ModelPickerDialog portal) - samggggflynn (NousResearch#27952 — dingtalk pre_start) Per references/batch-pr-salvage-may14-additions.md.
The dingtalk-stream SDK calls pre_start() on every registered handler before opening the WebSocket connection. Without this method, the SDK raises AttributeError and kills the stream connection, causing DingTalk to be unable to connect via Stream Mode.
Wraps _pt_print in try/except with a print() fallback. When a kanban worker's stdout is piped to a log file, prompt_toolkit raises NoConsoleScreenBufferError (Windows) or OSError (other) because there is no real console buffer. The fallback keeps worker output flowing instead of crashing.
…rch#28334) PR NousResearch#28330 was salvaged with a wrong noreply numeric ID (18091625 vs the correct 7065068). The commit on main is correctly authored to Grogger by username, but neither noreply form was in AUTHOR_MAP. Adds both so release-notes generation maps them to @Grogger.
resolve_xai_oauth_runtime_credentials() called _refresh_xai_oauth_tokens() with no try/except. A terminal refresh failure (HTTP 400/401/403 — invalid_grant, token revoked) propagated without clearing the dead access_token / refresh_token from auth.json, causing every subsequent session to retry the same doomed network request. Add a try/except around the refresh call that mirrors the existing credential_pool.py quarantine: when _is_terminal_xai_oauth_refresh_error identifies a non-retryable failure, clear the dead token fields from auth.json and write a last_auth_error diagnostic marker so future calls fail fast with a clear relogin_required error instead of hitting the network. active_provider is preserved (set_active=False) so multi-provider users whose chosen provider is not xai-oauth are unaffected. Tests: two new cases in test_auth_xai_oauth_provider.py cover terminal quarantine and transient pass-through.
…prompts (NousResearch#27644) The background review prompts (_SKILL_REVIEW_PROMPT and _COMBINED_REVIEW_PROMPT) now include explicit protection rules for bundled, hub-installed, and pinned skills — aligning with the curator's existing policy at curator.py L345/350. Before this change, bg-review could freely rewrite bundled skills like 'hermes-agent' or pinned skills, while the 7-day curator explicitly skips them. The review agent now sees: • Bundled skills (shipped with Hermes) • Hub-installed skills (installed via hermes skills install) • Pinned skills (marked via hermes curator pin) If only protected skills need updating, the review says 'Nothing to save.' and stops. Fixes NousResearch#27644
The dashboard's main column is `relative z-2` (App.tsx), which creates a stacking context that traps fixed descendants below the app sidebar (`z-50`). `ModelPickerDialog` renders `fixed inset-0 z-[100]` inline, so its z-100 is scoped to z-2 and the sidebar covers its left edge. The bug is visible across all themes but only obvious in the Large theme variants (Hermes Teal (Large), etc.) where the larger root font widens the dialog into the sidebar's column. Toast.tsx already documents the same trap and uses the same `createPortal(..., document.body)` escape. This commit ports the picker; the same pattern affects other inline z-[100] modals in the dashboard (OAuthLoginModal, Cron / Models / Profiles page modals) and is left for a follow-up — keeping this PR scoped to the reporter's specific case. Fixes NousResearch#28103 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the gateway receives SIGUSR1 (graceful restart via launchd_restart), the SIGUSR1 handler calls request_restart(via_service=True) and the gateway shuts down cleanly with exit code 0. However, the generated launchd plist uses KeepAlive → SuccessfulExit → false, meaning launchd only relaunches on *non-zero* exit codes. A clean exit(0) is treated as "successful, don't restart", so the gateway stays down after /restart, /update, or SIGUSR1. The systemd unit template already uses RestartForceExitStatus=75 for the same scenario. Mirror that convention: when _restart_via_service is True, raise SystemExit(75) so launchd's SuccessfulExit=false policy triggers a relaunch. Closes NousResearch#28135
Two code paths call json.loads() on output from external tools without catching JSONDecodeError. If the tool returns a non-JSON string (error message, empty string, or None), the entire call path crashes. 1. gateway/run.py — text_to_speech_tool() result in voice reply path. A TTS failure that returns an error string instead of JSON crashes the voice reply handler, killing the message response entirely. 2. cron/scheduler.py — skill_view() result when loading skills for cron jobs. A corrupted or missing skill file that returns an error string instead of JSON crashes the cron tick, preventing all jobs from executing that cycle. Both fixes catch (json.JSONDecodeError, TypeError), log a warning, and gracefully skip the failed operation instead of crashing.
…sections Two related bugs in gateway/config.py prevented per-platform gateway_restart_notification from working through config.yaml: 1. The shared-key bridging loop (load_gateway_config) omitted 'gateway_restart_notification', so the key never landed in platform_data['extra'] even when set under e.g. 'discord:' or 'mattermost:' sections. 2. PlatformConfig.from_dict() only read gateway_restart_notification from the top-level data dict, ignoring the 'extra' sub-dict where bridged keys are stored. Fix: add the key to the bridging loop, and add an 'extra' fallback in from_dict() so that round-tripped values (YAML → bridged → extra → from_dict) resolve correctly. Impact: users can now set gateway_restart_notification: false per platform in config.yaml instead of relying on env vars or the global platforms: block.
When the kanban auto-decomposer fans a triage task into child tasks, recompute_ready() immediately promotes parent-free children to 'ready' so the dispatcher picks them up. Some users want a manual workflow where children stay in 'todo' for review before dispatch. Add 'kanban.auto_promote_children' config key (default: true): - false: children stay in 'todo' after decomposition - true: existing behavior (auto-promote to 'ready') Changes: - kanban_db.py: decompose_triage_task() gains auto_promote param - kanban_decompose.py: reads auto_promote_children from config - kanban dashboard API: exposes the new setting in GET/PUT /orchestration Closes NousResearch#28016
…mespace The conversation_loop.py references _pool_may_recover_from_rate_limit which was defined in run_agent.py. After the conversation-loop extraction refactor, the helper was no longer in the same module scope. Wrap the call as _ra()._pool_may_recover_from_rate_limit() to route through the run_agent monkeypatch namespace where the helper is available. Adds regression test in test_gemini_fast_fallback.py. Fixes: MAILROOM Email Triage NameError, OPS Execution Monitor NameError.
Qwen3.x and DeepSeek-V3.x default to chatty/hallucinatory tool use without enforcement steering — agents narrate "calling tool X" without actually emitting a tool call, or run partial loops. Both model families fit the same failure pattern TOOL_USE_ENFORCEMENT_GUIDANCE was already injected for (gpt, codex, gemini, gemma, grok, glm). Co-authored-by: briandevans <252620095+briandevans@users.noreply.github.com> Squashed salvage of: - 403e567 fix(agent): add qwen and deepseek to TOOL_USE_ENFORCEMENT_MODELS - 9433eab test(agent): use realistic qwen-plus identifier in enforcement test Fixes NousResearch#28079.
The _SLACK_TARGET_RE regex only matched IDs starting with C (channel), G (group), or D (direct message). Slack user IDs start with U, causing 'Could not resolve' errors when trying to send DMs to specific users. Changes: - Expand _SLACK_TARGET_RE to accept U-prefixed IDs (user IDs) - Add conversations.open fallback to resolve user IDs to DM channel IDs before sending, since chat.postMessage requires a conversation ID Fixes #ISSUE_NUMBER
…salvage (NousResearch#28594) Adds the canonical noreply form (54813621+xxxigm@users.noreply.github.com) alongside the existing plain-email mapping so the salvage commit for @xxxigm's codex doctor PR doesn't fail AUTHOR_MAP CI.
…ousResearch#27975 `hermes doctor` printed 'codex CLI not installed (optional — ...)' as a generic info line at the bottom of the auth section, several rows below 'OpenAI Codex auth (not logged in)' and after MiniMax/Gemini auth checks. Users reading sequentially mistook it for MiniMax-related advice. Move the hint up under the Codex auth warning so it's adjacent to the row it actually pertains to. Behavior unchanged when the codex CLI is installed (success path keeps its 'codex CLI ✓' row at the bottom). Tests cover both placement and suppression cases. Salvage of @xxxigm's 3-commit stack (NousResearch#27986). Closes NousResearch#27975.
…#28626) Sweep of all CI failures on origin/main, grouped by drift source: Telegram allowlist gate (db50af9 added user-authz to _should_process_message): - Hardcoded "[Telegram]" prefix in the logger.warning so the call no longer dereferences self.name → self.platform, which test fixtures built via object.__new__ never set. - test_telegram_format / test_allowed_channels_widening fixtures stub _is_callback_user_authorized → True so the new gate doesn't reject guest-mode / allowed-channels test messages. - test_telegram_approval_buttons::test_update_prompt_callback_not_affected sets TELEGRAM_ALLOWED_USERS="*" so the fail-closed default doesn't reject the callback before it writes .update_response. Approval surface (6d495d9 renamed status, 214b953 detached stdin): - test_no_callback_returns_approval_required: status is now "pending_approval" (was "approval_required"). - test_close_stdin_allows_eof_driven_process_to_finish: switch to use_pty=True; non-PTY now uses stdin=DEVNULL. Mattermost (send() now resolves root_id via _api_get first): - test_send_with_thread_reply mocks _session.get with a thread-root response so the new resolver doesn't TypeError on a bare AsyncMock. Kanban (d8ad431 rename, f55d94a review column, _kanban_worker_skill_available): - _safe_int → _to_epoch in the two test_kanban_db tests. - Spawn-skills tests (×3) monkey-patch _kanban_worker_skill_available to True since the isolated kanban_home fixture has no devops/kanban-worker tree. - test_gateway_dispatcher_disables_corrupt_board: connect count 3 → 5 (review-column probe now also runs per tick). Aux-config severity at_or_above (a94ddd8): - test_diagnostics_endpoint_severity_filter expects warning filter to include error+critical now (was exact-match). Anthropic error handling (conversation loop extracted from run_agent): - _no_backoff_wait fixture patches BOTH run_agent.jittered_backoff AND agent.conversation_loop.jittered_backoff. The latter is the actual call site; without the second patch tests burn ~2s per retry and hit the 30s SIGALRM timeout on CI. Other test pollution / drift: - test_auto_does_not_select_copilot_from_github_token: patch agent.bedrock_adapter.has_aws_credentials → False so boto3's credential chain can't auto-pick Bedrock from developer ~/.aws. - test_setup_openclaw_migration: patch hermes_cli.gateway.get_env_value in addition to setup_mod.get_env_value — _platform_status reads through the gateway module's binding. - test_gateway_prefix: COMPONENT_PREFIXES["gateway"] now includes "hermes_plugins" too. - test_recommended_update_command_defaults_to_hermes_update: also short-circuit get_managed_update_command in case a stray ~/.hermes/.managed marker is present. - test_user_id_is_not_explicit: _parse_target_ref now returns is_explicit=False for Slack U.../W... IDs (chat.postMessage rejects them — a DM must be opened first via conversations.open).
… on failure (NousResearch#28669) Catch the PR NousResearch#28452 failure mode (orphan merge-conflict markers in hermes_cli/config.py) on the user side: after git pull succeeds, compile the files every 'hermes' invocation imports at startup. If any has a syntax error, git reset --hard back to the pre-pull SHA so the install stays bootable. User can retry once a fix lands upstream. - New _capture_head_sha() + _validate_critical_files_syntax() helpers - Wires both into _cmd_update_impl after the pull/reset succeeds - Tests cover the helpers, the rollback flow, and a production-tree invariant (CI fails if main itself has a syntax error in a critical file — catches future broken commits before users hit them)
When 'hermes update' syncs bundled skills, the summary line only shows the count of user-modified skills that were kept (e.g. '3 user-modified (kept)'), but not *which* skills. Once the update finishes, the user has no way to know which skills need triage. Append the skill names to the summary line, truncated to 5 with a '+N more' suffix for long lists: Done: 12 new, 3 updated, 7 unchanged, 3 user-modified (kept): hermes-agent, debugging-hermes-tui-commands, system-health. 25 total bundled. Closes NousResearch#28121
NousResearch#28063 fixed the macOS `/tmp`→`/private/tmp` symlink issue by checking the RAW path (pre-resolve) against startswith('/tmp/'). That works on Linux + macOS but not on Windows — Path('/tmp/foo').resolve() returns C:\\tmp\\foo and isn't the real Windows temp anyway. Replace the hardcoded '/tmp/' prefix with Path(tempfile.gettempdir()). resolve() + Path.relative_to() — same idiom as the cwd branch just below. Works correctly on Linux (/tmp), macOS (/private/var/folders/...), and Windows (%LOCALAPPDATA%\\Temp). Test rewritten to use tempfile.gettempdir() so the assertion exercises the same code path on every platform. Conflict against the just-merged NousResearch#28063 (raw_path approach) resolved by replacing the whole raw_path block — tempfile.gettempdir() is strictly better than that intermediate fix. Salvage of NousResearch#28262 by @Zyrixtrex.
…h#28680) Follow-up to NousResearch#28452. detect_stale_running() was calling _record_task_failure() on every reclaim, which ticked the consecutive_failures counter. With the default failure_limit=2, two legitimately long-running tasks (>4 h without explicit heartbeat) would auto-block via the spawn-failure circuit breaker — even though no worker actually failed. Stale reclaim is dispatcher-side absence-of-heartbeat detection, not a worker fault. Removed the _record_task_failure() call; the 'stale' event in task_events is still the audit surface, but the failure counter is now reserved for spawn_failed / timed_out / crashed (real failures). Also documents the heartbeat requirement: - KANBAN_GUIDANCE in agent/prompt_builder.py now states the rule ('call kanban_heartbeat at least once an hour for tasks running longer than 1 hour') so workers learn the contract. - kanban.md adds the stale event row to the events table and flags the heartbeat requirement in the worker lifecycle list. New regression test: test_detect_stale_does_not_tick_failure_counter locks in the new behaviour.
NousResearch#28672, NousResearch#28674, NousResearch#28676, NousResearch#28678) Five small fixes against issues filed during the post-merge salvage audit: * NousResearch#28670: `_GATEWAY_PROVIDER_ERROR_RE` false-positives on legitimate prose. Replace the regex with an anchored `_GATEWAY_PROVIDER_ERROR_SHAPE_RE` and add a length-cap heuristic to `_looks_like_gateway_provider_error`: short envelope at the start of the message → real provider error; long prose containing 'HTTP 404' → assistant answer, leave alone. * NousResearch#28672: drop the pointless 1s asyncio.sleep on Telegram thread-not-found retries. The same-thread retry is preserved (catches Telegram's occasional transient flake exercised by test_send_retries_transient_thread_not_found_before_fallback) but with no artificial delay. * NousResearch#28674: broaden `_should_retry_without_dm_topic_reply_anchor` to also fire when Bot API rejects `direct_messages_topic_id` for synthetic / resumed sends that have no reply anchor. Avoids dropping post-resume background notifications if the topic id goes stale. * NousResearch#28676: delete the dead image-document branch superseded by bd0c54d (which returns early on the same extension set). * NousResearch#28678: extend chat-scoped allowlist (`TELEGRAM_GROUP_ALLOWED_CHATS`) to also cover `chat_type == 'channel'`, so operators can authorize channel posts by chat id without falling back to per-user allowlists. Tests: - scripts/run_tests.sh tests/gateway/test_telegram_thread_fallback.py -q → 41/41 - scripts/run_tests.sh tests/cron/test_scheduler.py -q → 127/127 - broader test set: same 3 pre-existing test-pollution failures reproduce on plain main.
…ectory with 2 updates Bumps the actions-minor-patch group with 2 updates in the / directory: [google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml](https://github.com/google/osv-scanner-action) and [sigstore/gh-action-sigstore-python](https://github.com/sigstore/gh-action-sigstore-python). Updates `google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml` from 2.3.5 to 2.3.8 - [Release notes](https://github.com/google/osv-scanner-action/releases) - [Commits](google/osv-scanner-action@c518547...9a49870) Updates `sigstore/gh-action-sigstore-python` from 3.0.0 to 3.3.0 - [Release notes](https://github.com/sigstore/gh-action-sigstore-python/releases) - [Changelog](https://github.com/sigstore/gh-action-sigstore-python/blob/main/CHANGELOG.md) - [Commits](sigstore/gh-action-sigstore-python@f514d46...04cffa1) --- updated-dependencies: - dependency-name: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml dependency-version: 2.3.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-minor-patch - dependency-name: sigstore/gh-action-sigstore-python dependency-version: 3.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.7.0 to 4.1.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](docker/login-action@c94ce9f...4907a6d) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.19.2 to 7.1.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](docker/build-push-action@10e90e3...bcafcac) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](actions/setup-python@v5.3.0...a309ff8) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
…ng (NousResearch#28683) Follow-up to NousResearch#28455. The respawn guard's blocker_auth rule (last error matched a quota/auth/429 pattern) was auto-blocking the task on first occurrence. That's too aggressive: transient rate limits typically clear in seconds to minutes, but the auto-block puts the task in 'blocked' status which requires manual unblock. Now treats blocker_auth the same as recent_success and active_pr: defer the spawn this tick, leave the task in 'ready', let the next tick try again. If the auth error genuinely persists, the existing consecutive_failures counter trips the auto-block circuit breaker after failure_limit failures via the normal path — so a persistent 401/403/quota-exhausted still ends up blocked, just not on first hit. Also documents the respawn_guarded event in kanban.md's events table with the three guard reasons. Updated test_dispatch_respawn_guard_auto_blocks_auth_error → renamed to test_dispatch_respawn_guard_defers_auth_error_without_auto_block; asserts task stays in 'ready' and the guard reason is recorded.
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.3.1 to 6.0.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@34e1148...de0fac2) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Austin Pickett <pickett.austin@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ising KeyboardInterrupt (NousResearch#28688) The SIGTERM/SIGHUP handler raised KeyboardInterrupt() at the end of its agent-interrupt + grace-window sequence. Python delivers signals between bytecodes on the main thread, so when the signal hit mid-event-loop (typically inside prompt_toolkit's '_poll_output_size' coroutine's 'await asyncio.sleep()'), the KeyboardInterrupt unwound INTO that coroutine. prompt_toolkit's Task captured it as a BaseException; prompt_toolkit's '_handle_exception' then printed 'Unhandled exception in event loop' + the full asyncio traceback and parked the terminal on 'Press ENTER to continue...' before exiting. Same root cause as NousResearch#13710, different surface: there the failure was an EIO cascade after a logging-cache KeyError escaped the handler; here it's the KBI raise itself landing inside an asyncio Task. The fix is the same shape — let the event loop unwind on its own terms. Now: schedule 'app.exit()' via 'loop.call_soon_threadsafe()'. The prompt_toolkit Application returns normally from 'app.run()' and the existing '(EOFError, KeyboardInterrupt, BrokenPipeError)' handler in the input loop catches everything else. Fallback to 'raise KeyboardInterrupt()' preserved for contexts where prompt_toolkit isn't the active app (e.g. -q one-shot mode). The agent interrupt + 1.5 s grace window run unchanged before the new exit path, so subprocess-group cleanup ('os.killpg' on Linux) still gets its window. Tested live: external SIGTERM to the CLI (with 'kill <pid>') now exits cleanly with no traceback dump and no ENTER pause.
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.3 to 3.4.2. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.3.3...3.4.2) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.11 to 1.16.0. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](follow-redirects/follow-redirects@v1.15.11...v1.16.0) --- updated-dependencies: - dependency-name: follow-redirects dependency-version: 1.16.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](lodash/lodash@4.17.23...4.18.1) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.18.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [lodash-es](https://github.com/lodash/lodash) and [langium](https://github.com/eclipse-langium/langium/tree/HEAD/packages/langium). These dependencies needed to be updated together. Updates `lodash-es` from 4.17.23 to 4.18.1 - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](lodash/lodash@4.17.23...4.18.1) Updates `langium` from 4.2.1 to 4.2.3 - [Release notes](https://github.com/eclipse-langium/langium/releases) - [Changelog](https://github.com/eclipse-langium/langium/blob/main/packages/langium/CHANGELOG.md) - [Commits](https://github.com/eclipse-langium/langium/commits/HEAD/packages/langium) --- updated-dependencies: - dependency-name: lodash-es dependency-version: 4.18.1 dependency-type: indirect - dependency-name: langium dependency-version: 4.2.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [python-multipart](https://github.com/Kludex/python-multipart) from 0.0.22 to 0.0.27. - [Release notes](https://github.com/Kludex/python-multipart/releases) - [Changelog](https://github.com/Kludex/python-multipart/blob/main/CHANGELOG.md) - [Commits](Kludex/python-multipart@0.0.22...0.0.27) --- updated-dependencies: - dependency-name: python-multipart dependency-version: 0.0.27 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](theskumar/python-dotenv@v1.2.1...v1.2.2) --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.2.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](fastify/fast-uri@v3.1.0...v3.1.2) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@babel/plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-modules-systemjs) from 7.29.0 to 7.29.4. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.29.4/packages/babel-plugin-transform-modules-systemjs) --- updated-dependencies: - dependency-name: "@babel/plugin-transform-modules-systemjs" dependency-version: 7.29.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
* fix: update design system package, replace bg image, remove sync assets * fix(web): update bundled asset metadata * fix(web): normalize npm lockfile metadata * fix(nix): refresh npm lockfile hashes * chore(ci): trigger PR checks * fix(web): declare motion peer dependency * fix(nix): refresh npm lockfile hashes * chore(ci): trigger PR checks after dependency update * fix(web): restore cross-platform lockfile entries * fix(nix): refresh npm lockfile hashes * chore(ci): trigger PR checks after lockfile restore --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Daily sync with upstream NousResearch/hermes-agent.
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
43 |
unresolved-import |
35 |
invalid-argument-type |
20 |
invalid-assignment |
9 |
unresolved-reference |
2 |
unsupported-operator |
2 |
not-subscriptable |
1 |
invalid-return-type |
1 |
First entries
tests/gateway/test_telegram_callback_auth_fail_closed.py:27: [unresolved-attribute] unresolved-attribute: Unresolved attribute `NetworkError` on type `ModuleType`
tests/plugins/test_kanban_worker_runs.py:20: [unresolved-import] unresolved-import: Cannot resolve imported module `fastapi.testclient`
tests/cli/test_update_command.py:23: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_telegram_channel_posts.py:51: [unresolved-attribute] unresolved-attribute: Unresolved attribute `ext` on type `ModuleType`
tests/gateway/test_send_voice_reply_notify.py:16: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/agent/test_skill_bundles.py:7: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_telegram_callback_auth_fail_closed.py:26: [unresolved-attribute] unresolved-attribute: Unresolved attribute `BadRequest` on type `ModuleType`
tests/tools/test_send_message_telegram_proxy.py:23: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_telegram_audio_vs_voice.py:24: [unresolved-reference] unresolved-reference: Name `GatewayRunner` used when not defined
tests/tools/test_kanban_tools.py:1714: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["board"]` on object of type `list[Unknown]`
tests/hermes_cli/test_kanban_db.py:597: [unresolved-attribute] unresolved-attribute: Attribute `last_failure_error` is not defined on `None` in union `Task | None`
agent/azure_identity_adapter.py:499: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
tests/gateway/test_telegram_channel_posts.py:36: [unresolved-attribute] unresolved-attribute: Unresolved attribute `ContextTypes` on type `ModuleType`
tests/gateway/test_telegram_callback_auth_fail_closed.py:43: [unresolved-attribute] unresolved-attribute: Unresolved attribute `constants` on type `ModuleType`
tests/agent/test_auxiliary_client_azure_foundry.py:32: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_telegram_channel_posts.py:49: [unresolved-attribute] unresolved-attribute: Unresolved attribute `HTTPXRequest` on type `ModuleType`
tests/tools/test_tirith_security.py:1340: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `dict[Unknown, Unknown]`, found `Literal["not a dict"]`
agent/auxiliary_client.py:2949: [invalid-argument-type] invalid-argument-type: Argument to function `resolve_provider_client` is incorrect: Expected `str`, found `None | (Any & ~AlwaysFalsy)`
tests/gateway/test_telegram_channel_posts.py:26: [unresolved-attribute] unresolved-attribute: Unresolved attribute `Message` on type `ModuleType`
gateway/run.py:15733: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["metadata"]` and value of type `dict[str, Any] | None | dict[str, str]` on object of type `dict[str, str]`
tests/gateway/test_telegram_channel_posts.py:53: [unresolved-attribute] unresolved-attribute: Unresolved attribute `request` on type `ModuleType`
tests/agent/test_auxiliary_client_azure_foundry.py:88: [unresolved-import] unresolved-import: Cannot resolve imported module `openai`
tests/tools/test_schema_sanitizer.py:618: [invalid-argument-type] invalid-argument-type: Argument to function `strip_slash_enum` is incorrect: Expected `list[dict[Unknown, Unknown]]`, found `None`
tests/gateway/test_telegram_callback_auth_fail_closed.py:25: [unresolved-attribute] unresolved-attribute: Unresolved attribute `TelegramError` on type `ModuleType`
hermes_cli/oneshot.py:324: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `dict[str, Any]`, found `(Any & ~AlwaysFalsy & ~Top[dict[Unknown, Unknown]]) | (list[Unknown] & ~AlwaysFalsy) | (list[Any & Top[dict[Unknown, Unknown]]] & ~AlwaysFalsy) | None`
... and 88 more
✅ Fixed issues (28):
| Rule | Count |
|---|---|
invalid-argument-type |
21 |
unresolved-attribute |
3 |
unresolved-reference |
2 |
invalid-assignment |
1 |
unsupported-operator |
1 |
First entries
tests/hermes_cli/test_kanban_db.py:1440: [invalid-argument-type] invalid-argument-type: Argument to function `_safe_int` is incorrect: Expected `str | None`, found `Literal[0]`
agent/conversation_loop.py:2320: [unresolved-reference] unresolved-reference: Name `_pool_may_recover_from_rate_limit` used when not defined
tests/stress/test_subprocess_e2e.py:21: [unresolved-reference] unresolved-reference: Name `Path` used when not defined
tests/hermes_cli/test_aux_config.py:50: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["max_concurrency"]` on object of type `list[Unknown]`
tests/hermes_cli/test_aux_config.py:46: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> str, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[str]]` cannot be called with key of type `Literal["session_search"]` on object of type `list[str]`
hermes_cli/kanban_db.py:4657: [invalid-argument-type] invalid-argument-type: Argument to function `_safe_int` is incorrect: Expected `str | None`, found `int | None`
agent/agent_init.py:1398: [invalid-argument-type] invalid-argument-type: Argument to function `query_ollama_num_ctx` is incorrect: Expected `str`, found `(Unknown & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (dict[str, str] & ~AlwaysFalsy) | Literal[""]`
hermes_cli/kanban_db.py:4655: [invalid-argument-type] invalid-argument-type: Argument to function `_safe_int` is incorrect: Expected `str | None`, found `int`
agent/context_compressor.py:1059: [invalid-argument-type] invalid-argument-type: Argument to function `call_llm` is incorrect: Expected `list[Unknown]`, found `str | dict[str, str] | list[dict[str, str]] | int`
gateway/platforms/api_server.py:623: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to `def create_job(prompt: str | None, schedule: str, name: str | None = None, repeat: int | None = None, deliver: str | None = None, origin: dict[str, Any] | None = None, skill: str | None = None, skills: list[str] | None = None, model: str | None = None, provider: str | None = None, base_url: str | None = None, script: str | None = None, context_from: str | list[str] | None = None, enabled_toolsets: list[str] | None = None, workdir: str | None = None, no_agent: bool = False) -> dict[str, Any]`
tests/hermes_cli/test_aux_config.py:50: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> LiteralString, (key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str]` cannot be called with key of type `Literal["max_concurrency"]` on object of type `str`
gateway/platforms/telegram.py:2216: [unresolved-attribute] unresolved-attribute: Attribute `MARKDOWN` is not defined on `None` in union `Unknown | None`
hermes_cli/auth.py:5355: [invalid-argument-type] invalid-argument-type: Argument to bound method `dict.get` is incorrect: Expected `str`, found `(str & ~Literal["spotify"] & ~Literal["nous"] & ~Literal["openai-codex"] & ~Literal["xai-oauth"] & ~Literal["qwen-oauth"] & ~Literal["google-gemini-cli"] & ~Literal["minimax-oauth"] & ~Literal["copilot-acp"]) | None`
agent/context_compressor.py:1059: [invalid-argument-type] invalid-argument-type: Argument to function `call_llm` is incorrect: Expected `int | float`, found `str | dict[str, str] | list[dict[str, str]] | int`
agent/agent_init.py:770: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method dict[str, str].__getitem__(key: str, /) -> str` cannot be called with key of type `slice[Literal[-4], None, None]` on object of type `dict[str, str]`
agent/agent_init.py:770: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method dict[str, str].__getitem__(key: str, /) -> str` cannot be called with key of type `slice[None, Literal[8], None]` on object of type `dict[str, str]`
tests/hermes_cli/test_aux_config.py:46: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["session_search"]` on object of type `list[Unknown]`
tests/test_subprocess_home_isolation.py:73: [unresolved-attribute] unresolved-attribute: Attribute `endswith` is not defined on `None` in union `str | None`
tests/hermes_cli/test_aux_config.py:46: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> LiteralString, (key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str]` cannot be called with key of type `Literal["session_search"]` on object of type `str`
tests/hermes_cli/test_kanban_db.py:1441: [invalid-argument-type] invalid-argument-type: Argument to function `_safe_int` is incorrect: Expected `str | None`, found `Literal[1700000000]`
agent/context_compressor.py:1059: [invalid-argument-type] invalid-argument-type: Argument to function `call_llm` is incorrect: Expected `str`, found `str | dict[str, str] | list[dict[str, str]] | int`
hermes_cli/kanban_db.py:4661: [unsupported-operator] unsupported-operator: Operator `-` is not supported between objects of type `int & ~AlwaysFalsy` and `int | None`
agent/agent_init.py:772: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method dict[str, str].__getitem__(key: str, /) -> str` cannot be called with key of type `slice[None, Literal[20], None]` on object of type `dict[str, str]`
gateway/run.py:16140: [invalid-argument-type] invalid-argument-type: Argument to function `maybe_auto_title` is incorrect: Expected `((str, /) -> None) | None`, found `Any | None | dict[str, Any | None]`
agent/context_compressor.py:1059: [invalid-argument-type] invalid-argument-type: Argument to function `call_llm` is incorrect: Expected `dict[Unknown, Unknown]`, found `str | dict[str, str] | list[dict[str, str]] | int`
... and 3 more
Unchanged: 4595 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
Daily sync with upstream. Auto-created by cron job.
New upstream commits (2080):
ff0a703 fix(web): consume bundled design system assets (NousResearch#26391)
070eeaa chore(deps): bump @babel/plugin-transform-modules-systemjs in /website
43f8edb chore(deps): bump fast-uri from 3.1.0 to 3.1.2 in /website
a9c38c7 chore(deps): bump python-dotenv from 1.2.1 to 1.2.2
dffcb6f chore(deps): bump python-multipart from 0.0.22 to 0.0.27
7f1d124 chore(deps): bump lodash-es and langium in /website
c4bcc77 chore(deps): bump lodash from 4.17.23 to 4.18.1 in /website
0b75d24 chore(deps): bump follow-redirects from 1.15.11 to 1.16.0 in /website
fc90f1b chore(deps): bump dompurify from 3.3.3 to 3.4.2 in /website
f1254b1 fix(cli): exit prompt_toolkit cleanly on SIGTERM/SIGHUP instead of raising KeyboardInterrupt (NousResearch#28688)
709e37e fix(dashboard): add scheduled kanban i18n strings (NousResearch#28534)
c498116 chore(actions)(deps): bump actions/checkout from 4.3.1 to 6.0.2
7bcdced fix(kanban): respawn guard defers blocker_auth instead of auto-blocking (NousResearch#28683)
b10b783 chore(actions)(deps): bump actions/setup-python from 5.3.0 to 6.2.0
bbee1dd chore(actions)(deps): bump docker/build-push-action from 6.19.2 to 7.1.0
2692457 chore(actions)(deps): bump docker/login-action from 3.7.0 to 4.1.0
424f2cc chore(actions)(deps): bump the actions-minor-patch group across 1 directory with 2 updates
a3c7531 fix(telegram): address post-merge audit follow-ups (NousResearch#28670, NousResearch#28672, NousResearch#28674, NousResearch#28676, NousResearch#28678)
88ee58f fix(kanban): stale reclaim must not tick failure counter (NousResearch#28680)
7f253f5 fix(acp): use tempfile.gettempdir() in workspace auto-approve
58591d9 feat: show names of user-modified skills in bundled skill sync summary
aedb8ac feat(update): syntax-validate critical files post-pull, auto-rollback on failure (NousResearch#28669)
a0bd11d fix(tests): catch up 25 stale tests after recent merges (NousResearch#28626)
12c3983 fix(doctor): attach codex CLI hint to OpenAI Codex auth warning for NousResearch#27975
4039e2a chore(release): alias xxxigm noreply for upcoming NousResearch#27986 salvage (NousResearch#28594)
62573f4 fix: guard yaml.safe_load, flock unlock, TOCTOU races, and atomic writes
d759a67 fix: add recovery hints to loop guard warnings
87c6edc fix(skills): add timeout to Google OAuth urlopen calls
b8a9cbd fix: tolerate unreadable gateway JSONL transcripts
663ee14 fix(cron): allow emoji ZWJ sequences in prompts
...