sync: catch up ~469 commits from upstream (owner review) — resolves #562 escalation#576
Conversation
…2372) The /learn authoring prompt taught a subset of the HARDLINE skill rules, and stated the <=60-char description rule without making the model enforce it — so generated descriptions overshot (up to 202 chars), which the 60-char system-prompt skill index then silently truncates. - description: add the index-truncation rationale, a count-and-trim self-check, and a good/bad length example so the model actually hits <=60. - add platforms-gating rule (OS-bound primitives -> declare platforms:). - add author-credits-human-first rule. - round out the Hermes-tool framing with the full wrapped-tool mapping and references/templates layout. Closes #52367.
The verify-on-stop guard (PRs #52296, #52297) defaulted ON for every session, so on gateway messaging surfaces (Telegram, Discord, etc.) the model complied with the nudge by writing a hermes-verify temp script and emitting an ad-hoc verification summary, which the gateway delivered to the end user as chat noise. Resolve a surface-aware default instead. The DEFAULT_CONFIG value becomes the sentinel "auto", which verify_on_stop_enabled() resolves to ON for interactive coding surfaces (CLI, TUI, desktop) and programmatic callers, and OFF for conversational messaging surfaces. The surface is read from HERMES_SESSION_PLATFORM (what the gateway actually binds), with HERMES_SESSION_SOURCE and HERMES_PLATFORM as fallbacks, matching the sibling resolution in skill_commands.py and prompt_builder.py. An explicit HERMES_VERIFY_ON_STOP env var or a boolean agent.verify_on_stop config still overrides in either direction. The passive evidence ledger and the call site are untouched.
Block scale-to-zero suspend while background async delegations are active, and restore runtime status to running on real inbound after a dormant wake.\n\nAdd regression coverage for both review findings.
fixes stacked PRs no-checks bug where main < a < b a merges into main b is retargeted to main but b doesn't run checks since it's not considered a new pr to main now b will simply already have passing ci :)
The desktop self-updater rebuilds and re-signs the .app on each user's own machine (`hermes desktop --build-only` -> electron-builder `--dir`). With CSC_IDENTITY_AUTO_DISCOVERY on (its default), electron-builder signs the type=distribution, hardened-runtime bundle with whatever identity is in that user's keychain -- typically a personal "Apple Development" cert -- which stalls/fails the sign step (no Developer ID, no provisioning profile) or clobbers the original notarized signature with an unusable one, tripping Gatekeeper on every post-update launch. Force ad-hoc signing for the local packaged rebuild instead: deterministic, and exactly what _desktop_macos_relaunchable_fixup already finishes off. No-op for source runs, off-macOS, when a real identity is configured (CSC_LINK / APPLE_SIGNING_IDENTITY), or when the caller already pinned the flag.
fix(desktop): ad-hoc sign macOS self-update rebuilds
…t finalize_turn #48879 closed the tool-call sequence on interrupt inside finalize_turn so a /stop after a tool no longer persists a `tool` tail that the next user message turns into a `tool -> user` role-alternation violation (which strict providers like Gemini/Claude react to by hallucinating a continuation and ignoring prior context — what users see as "lost context after stop"). But the retry-wait, error-handling, and post-error retry-wait interrupt aborts in conversation_loop return early and never reach finalize_turn, so they still persisted and returned a raw `tool` tail. Interrupting during provider backoff/rate-limiting (common under heavy work) hit exactly this path. Extract the close into a shared close_interrupted_tool_sequence helper and apply it at every interrupt abort (finalize_turn + the three early returns) so the whole bug class is fixed, not just the one site.
…a busy retry A prompt sent while a turn was in flight got rejected with 4009 "session busy", which pushed clients (the desktop app) into a deadline-bounded busy-retry. When turn teardown outlived that deadline — e.g. the user hits stop while a slow, non-interruptible tool (web_search, read_file, an MCP call) is mid-flight, since the sequential executor only checks the interrupt flag between tools — the resubmitted message was silently dropped: "it just doesn't listen". Wire the previously-dead display.busy_input_mode config into prompt.submit: instead of rejecting, apply the policy and queue the message to run as the next turn (drained in run()'s tail, ahead of goal/notification follow-ups). Modes: interrupt (default) interrupts the live turn so it winds down promptly then runs the queued message; queue runs it after the current turn finishes; steer injects it into the live turn when accepted, else queues. The queued slot pins the sender's transport and losslessly merges a second arrival. No client deadline, no dropped sends.
…eq-sibling-paths fix(agent): close tool-call sequence on all interrupt aborts (#48879 follow-up)
Pipe one password line per sudo invocation in compound commands so a correct password is not rejected on the second `sudo` in `sudo a && sudo b`. Drop the session cache when sudo returns Authentication failed, surface sudo_auth_failed in the tool result, and add hints for interactive sessions.
Replace _count_real_sudo_invocations (which called _rewrite_real_sudo_invocations and discarded the rewritten string) with a lightweight token scan that reuses the same tokeniser but skips string building. Remove the agent-facing tip about nested sudo in heredocs — the cache-cleared warning is enough.
fix(tui_gateway): queue mid-turn prompts instead of dropping them on a busy retry
atomic_yaml_write used default yaml.dump which emits indentless sequences (list items at column 0), while atomic_roundtrip_yaml_update (ruamel.yaml) emits 2-space-indented sequences. Cross-path writes to the same config.yaml toggled indentation on every save, eventually producing a mixed-indent file that js-yaml rejects with 'bad indentation of a mapping entry', silently dropping custom_providers and breaking model switching. Add IndentDumper SafeDumper subclass that forces indentless=False, route atomic_yaml_write through it. Route tui_gateway._save_cfg and the Telegram adapter's config writer through atomic_yaml_write so all paths emit the same 2-indent layout. Salvaged from #32034 by @xxxigm. Adapted to current main which already has allow_unicode=True (from #51356) but was missing IndentDumper. Closes #31999
… timeout (#29184) During stdio MCP server startup, _run_stdio (an async method) called the synchronous check_package_for_malware() inline. That makes a blocking urllib HTTPS POST to api.osv.dev whose own timeout doesn't reliably cover a stalled SSL handshake, so an intermittent network issue froze the entire asyncio event loop for up to ~120s — blowing past the TUI/gateway's 15s startup budget and showing "gateway startup timeout". Run the check via asyncio.to_thread (off the loop) AND bound it with asyncio.wait_for(timeout=_OSV_MALWARE_CHECK_TIMEOUT_S=12s). The malware check is fail-open, so on timeout we log and proceed rather than blocking startup. Salvaged from #29190 by @qdaszx (re-applied on current main — the call site moved since the PR was opened), combining the to_thread approach also proposed in #29192 by @ygd58. Two load-bearing tests: event-loop-not-blocked-during- check and timeout-fails-open — both mutation-verified to fail against the old inline blocking call. Closes #29184. Co-authored-by: ygd58 <buraysandro9@gmail.com>
fix(utils): unify YAML list indent across all config writers (#31999)
…blocking fix(mcp): run OSV malware preflight off the event loop with a bounded timeout (#29184)
…put (#14185)
todo_tool crashed with `AttributeError: 'str' object has no attribute 'get'`
when the LLM emitted the `todos` param as a JSON-encoded string instead of an
array, or as a list containing non-dict items (observed intermittently on
Claude 4.5/4.6/4.7, and after a prior tool-call rejection where the model
"self-corrects" by wrapping the list in json.dumps).
Three additive guards, no behavior change for well-formed input:
- todo_tool(): if `todos` is a str, json.loads it; reject unparseable strings
and non-list values with a clear tool_error instead of crashing downstream.
- _validate(): non-dict items return a {id:"?", content:"(invalid item)"}
placeholder rather than calling .get() on a str/int/None.
- _dedupe_by_id(): non-dict items get a synthetic key so _validate handles them.
Salvaged from #14785 by @Tranquil-Flow (authorship preserved via cherry-pick).
Comprehensive tests: JSON-string coercion (parse / unparseable / non-list /
non-string), non-dict list items (str/None/int/mixed), and a well-formed-
unchanged regression class — both guards mutation-verified to fail without them.
Closes #14185. Supersedes #14187, #22505, #14350 (same fix, less/no test
coverage) and #16952 (bundled unrelated scope-creep).
When the gateway persists a user message after a transient provider failure (429/timeout/auth error), subsequent retries of the same Telegram message could stack duplicate user turns in the transcript, causing the agent to fall behind by 1-2 messages. Add has_platform_message_id() to SessionDB (using the existing idx_messages_platform_msg_id partial index) and a SessionStore wrapper. The gateway's transient-failure path checks this before append_to_transcript -- if the platform_message_id is already persisted, the duplicate write is skipped. Salvaged from #47869 by @davidgut1982. Adapted to current main which has additional append sites and an existing content-based dedupe in the exception handler path. Closes #47237
…-turns fix(gateway): dedupe user turns on transient failure (#47237)
Switching sessions in the desktop app could freeze the whole UI for several seconds on heavy, tool-rich chats. Root causes and fixes: - Cold `session.resume` built the AIAgent (MCP discovery, prompt/skill build) *before* returning, and the desktop awaits that RPC before it paints — so the entire switch blocked on the build. Add an opt-in `defer_build` resume path (the contract `session.create` already uses): return the full display transcript immediately, register an upgradable live session, and pre-warm the agent on a short timer. The persisted runtime identity (model/provider/base_url/api_mode/reasoning/tier) is restored on the deferred build so it can't drop the provider. - Nothing bounded how many in-memory agents accumulate; a user who reconnects often piled up detached sessions for the full 6h TTL. Add a soft LRU cap (`max_live_sessions`, default 16) that evicts the least-recently-active DETACHED sessions (no live client) — never a running, awaiting-input, mid-build, or live-transport one. Reopening re-resumes from disk. - On the prefetch-hit cold-resume path, skip rebuilding a throwaway merged-message array (and its 1000-entry Map) when the prefetch already painted the exact transcript; the downstream sameMessageList guard already drops the publish, so it was pure main-thread cost. The desktop opts into `defer_build` for every non-watch cold resume; the eager path stays for CLI/TUI and existing callers.
Per review: gating the faster path behind a `defer_build` flag that the only caller always sends is pointless. Flip it — `session.resume` now defers the agent build by default for every caller (desktop + Ink TUI); a caller that needs the agent built synchronously passes `eager_build: true` (used by the build-race test). The desktop no longer sends a flag. While verifying the flip, fixed two real parity gaps the deferred path had vs the old eager (`_init_session`) path: - `_enable_gateway_prompts()` was never called on a deferred resume, so approvals/clarify wouldn't route through the gateway prompt callbacks. - `_start_agent_build` never wired `background_review_callback` / `memory_notifications`, so a deferred-built session's self-improvement "💾 …" summary leaked to stdout instead of rendering in-transcript. Wiring it there also fixes it for `session.create` sessions, which build through the same path. ACP is unaffected (it uses its own session_manager, not this RPC); the Ink TUI already consumes the same lazy `info` shape from session.create and upgrades on the later `session.info` event.
Collapse the duplicated cold-resume / lazy-watch / create scaffolding into shared helpers: _deferred_session_record (the live-session dict minus the agent), _lazy_resume_info (the not-yet-built session.info), _claim_or_reuse_live (lock + double-checked register-or-reuse), and _schedule_agent_build (the pre-warm timer). Net -12 lines, three copies of the ~30-key session dict and the lazy-info block down to one each. No behavior change.
Statusbar items declared a 'title' string (e.g. YOLO, gateway health, agents, cron, version, context usage) that was populated by use-statusbar-items.tsx but never forwarded to the rendered DOM in StatusbarControls — so every statusbar button/menu/text/link had no hover hint. Wrap the four render branches (menu trigger, text, link, action) in the existing 'Tip' component from components/ui/tooltip.tsx. Tip is self-contained (carries its own Provider), instant (delayDuration=0), themed (bg-foreground/text-background, auto-inverts per theme), and already in use elsewhere in the desktop shell. Renders the child untouched when label is falsy, so items without a title stay zero-cost.
These three assert the eager build contract — stored runtime overrides / profile db reach _make_agent synchronously, and the agent binds to the compression tip. Under deferred-by-default the build runs off-thread, so they raced the timer (green in CI, flaky locally). Pin them to eager_build; deferred coverage lives in the protocol tests.
…works Manual /compress and session hygiene auto-compress both create temporary AIAgent instances to run compression. These agents were created without a session_db, so compress_context computed the compressed messages in memory, rotated the session ID, and reported success — but never wrote to the database. The next user message reloaded the original full transcript, making compression appear to do nothing. Fix: pass session_db=self.session_store._db to both temp agents so the session rotation is properly persisted. Also set _end_session_on_close on the /compress temp agent (already done in hygiene path) to prevent cleanup from ending the newly rotated session.
- Replace getattr(self.session_store, '_db', None) with self._session_db (the GatewayRunner's own SessionDB, consistent with existing usage in slash_commands.py L240/L499). - Remove verbose comment referencing a branch name as an issue number. - Update stale comment in run.py that said 'today it has no session_db'. - Add regression test verifying session_db is passed and rotated session is persisted (adapted from #51624 by @LeonSGP43). - Add _session_db=None to _make_runner fixtures in test_compress_command, test_compress_focus, and test_compress_plugin_engine.
it's way too slow. just grep files lol
avoid duplicating work, avoid file discovery on each job
…#53399) The self-hosted OIDC provider fetched the discovery document with a bare httpx.get(). httpx defaults to follow_redirects=False (unlike curl -L or the requests library), so when an IDP answers GET /.well-known/openid-configuration with a 3xx — Authentik canonicalises the .well-known path, and any IDP behind a reverse proxy doing an http→https upgrade redirects too — the bare redirect (empty body) tripped the status != 200 guard and raised 'OIDC discovery returned 302', which routes.py maps to the provider_unreachable audit event and a 503. The browser surfaced 'Auth provider self-hosted unreachable'. The user's smoking gun (curl -o writing zero bytes from inside the container) is exactly a redirect with no body — the same wall the code hit. Add follow_redirects=True to the discovery GET only. It's safe: the issuer-pin check and _require_https_or_loopback still validate the resolved document and every endpoint, so a redirect can't smuggle in a bad issuer or a cleartext endpoint. The token/revocation POSTs deliberately keep the no-follow default (they carry an auth code / refresh token and the endpoint is already the canonical absolute URL). Existing discovery tests mocked httpx.get with a canned 200 and never exercised a real 3xx. Add a regression test that runs a real loopback server returning a 302 on the .well-known path — fails without the fix (ProviderError: discovery returned 302), passes with it.
Resolves #562 escalation. 18 conflict files resolved: CORE (verified, no param-drop / wiring intact): - agent/conversation_loop.py: moa_config threaded through wrapper->_impl (no param-drop) - agent/retry_utils.py: union — fork extract_retry_after_seconds + upstream Z.AI overload backoff - agent/transports/codex.py: reconciled parallel cache_key impls (explicit -> content-address -> session_id) - cron/scheduler.py: symbol union (all fork+upstream defs), cron cache_key thread preserved - hermes_cli/config.py: upstream load_config dedupe refactor; pre_update_backup/DEFAULT_SOUL_MD relocated not lost - tools/mcp_tool.py, tools/terminal_tool.py: fork divergence kept + upstream changes applied (no symbol drop) CORRECTION-LEARNING (auto-merged, audited intact): - agent/turn_finalizer.py: upstream tool-seq-close (#48879) added before correction wiring; decide_correction_review intact - run_agent.py: moa_config forwarder threaded; _record_turn_correction/CorrectionLearner wiring intact NON-CORE: 5 workflows -> ours (newer pinned actions + PR-gating); scripts/release.py -> union AUTHOR_MAP; model-catalog.json -> theirs
…t-key design The upstream merge auto-unioned two CONTRADICTORY solutions to cron prompt-cache coldness: - fork: explicit cache_key threaded cron/scheduler.py -> Agent -> chat_completion_helpers -> transport (cron passes cache_key='cron_<id>'); interactive falls back to session_id; codex headers use cache_key. - upstream: content-address the static prefix (instructions+tools) via _content_cache_key(); interactive gets a 'pck_' hash; headers use session_id. rerere layered upstream's content-addressing as tier-2, silently changing the fork's tested INTERACTIVE behavior, and the auto-merged test file unioned both suites (mutually exclusive: 'cache_key == session_id' AND 'cache_key != session_id' can never both pass). Resolution: keep the fork's intentional, multi-file, tested explicit-key design (safest interpretation per evolution divergence policy). Reverted codex.py cache_key resolution to 'params.get(cache_key) or session_id'; removed now-dead _content_cache_key + its hashlib/json imports; dropped the 2 upstream-only content-addressing tests and reconciled the shared xai test to the fork's assertions. Both solve cron equally; nothing lost for cron. [REVIEW BEFORE MERGE] owner may prefer upstream's automatic content-addressing over the fork's explicit-key plumbing — flagged for verification.
…ork test Cross-review (agy/Gemini) on the cache_key reconciliation surfaced two real issues (its other two flags were snapshot-misreads — verified false): - build_kwargs docstring still described upstream content-addressing fallback that the prior commit reverted; corrected to describe session_id fallback. - test_session_id_sets_cache_key (fork-only) was silently dropped by the auto-merge of the test file; restored it (asserts no-cache_key -> session_id). agy confirmed preserving the fork's explicit-key design is correct (session_id enables monotonic per-turn prefix caching that a static content-hash cannot).
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 34301363 | Triggered | Generic Password | d93abd7 | tests/tools/test_terminal_tool.py | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
…cker.yml The #562 catch-up auto-merged upstream's ci.yml docker: job, which does uses: ./.github/workflows/docker.yml expecting a REUSABLE (workflow_call) workflow. The fork's docker.yml (kept via --ours; PR-gated standalone publish on push/pull_request/release) is NOT reusable, so GitHub rejected the entire CI workflow with 'workflow file issue' — blocking ALL CI checks (0 jobs ran). Fork architecture: docker.yml runs standalone; ci.yml only orchestrates the reusable lint/test/etc. workflows + docker-lint. Dropped the reusable docker: job (it was already excluded from the required all-checks-pass gate). Verified all other ci.yml 'uses:' targets are valid workflow_call workflows and tests.yml accepts the new slice_count input.
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
68 |
invalid-argument-type |
67 |
unresolved-import |
66 |
unsupported-operator |
16 |
invalid-assignment |
11 |
not-subscriptable |
4 |
invalid-method-override |
3 |
no-matching-overload |
2 |
unresolved-reference |
1 |
invalid-return-type |
1 |
not-iterable |
1 |
call-non-callable |
1 |
First entries
tests/hermes_cli/test_kanban_project_link.py:37: [unresolved-attribute] unresolved-attribute: Attribute `workspace_path` is not defined on `None` in union `Task | None`
tests/gateway/test_relay_upstream_authz.py:76: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `str | None`, found `Platform | str`
tests/agent/test_reasoning_stale_timeout_floor.py:154: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `bool`, found `str | bool`
tests/hermes_cli/test_setup.py:538: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/cli/test_cli_pet_pane.py:98: [unresolved-attribute] unresolved-attribute: Attribute `spritesheet` is not defined on `None` in union `InstalledPet | None`
gateway/run.py:8789: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_moa_restore_override` on type `MessageEvent`
tests/agent/test_pet_engine.py:12: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/cron/test_suggestions.py:213: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["monitor"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 37 union elements`
tests/hermes_cli/test_toolset_validation.py:7: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
agent/auxiliary_client.py:6259: [invalid-argument-type] invalid-argument-type: Argument to function `_build_call_kwargs` is incorrect: Expected `str`, found `str | None | Unknown`
tests/hermes_cli/test_aux_config.py:54: [unresolved-attribute] unresolved-attribute: Attribute `keys` is not defined on `str`, `list[Unknown]`, `list[str]`, `None`, `int`, `float` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 37 union elements`
tests/gateway/test_external_drain_control.py:173: [invalid-assignment] invalid-assignment: Object of type `bound method GatewayRunner._enter_external_drain() -> None` is not assignable to attribute `_enter_external_drain` of type `def _enter_external_drain(self) -> None`
tests/run_agent/test_tool_call_incremental_persistence.py:74: [unresolved-attribute] unresolved-attribute: Unresolved attribute `save_trajectories` on type `AIAgent`
tests/tools/test_web_providers.py:217: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["backend"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 37 union elements`
tests/hermes_cli/test_kanban_block_kinds.py:201: [unresolved-attribute] unresolved-attribute: Attribute `status` is not defined on `None` in union `Task | None`
gateway/run.py:8798: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_moa_disable_after_turn` on type `MessageEvent`
gateway/run.py:16718: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `dict[str, Any] | None`, found `Any | str | dict[Unknown, Unknown] | ... omitted 3 union elements`
tests/agent/test_pet_generate.py:353: [unresolved-attribute] unresolved-attribute: Attribute `generated` is not defined on `None` in union `InstalledPet | None`
tests/hermes_cli/test_dashboard_token_auth.py:301: [unresolved-attribute] unresolved-attribute: Object of type `_State` has no attribute `token_principal`
tests/hermes_cli/test_kanban_block_kinds.py:179: [unresolved-attribute] unresolved-attribute: Attribute `block_recurrences` is not defined on `None` in union `Task | None`
agent/pet/generate/atlas.py:1085: [unresolved-import] unresolved-import: Cannot resolve imported module `PIL`
tests/agent/test_verification_stop.py:5: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_projects_db.py:110: [unresolved-attribute] unresolved-attribute: Attribute `primary_path` is not defined on `None` in union `Project | None`
tests/plugins/model_providers/test_ollama_cloud_profile.py:15: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/run_agent/test_tool_call_incremental_persistence.py:72: [unresolved-attribute] unresolved-attribute: Unresolved attribute `tool_delay` on type `AIAgent`
... and 216 more
✅ Fixed issues (62):
| Rule | Count |
|---|---|
unsupported-operator |
15 |
invalid-argument-type |
14 |
unresolved-attribute |
11 |
unresolved-import |
9 |
no-matching-overload |
5 |
invalid-assignment |
3 |
invalid-return-type |
2 |
not-subscriptable |
1 |
unresolved-reference |
1 |
invalid-parameter-default |
1 |
First entries
tools/browser_tool.py:1196: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str`, `list[Unknown]`, `list[str]`, `None`, `int`, `float` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/tools/test_stage2_hook_user_flag_guard.py:31: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_stage2_hook_puid_pgid.py:22: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_aux_config.py:47: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["session_search"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
gateway/run.py:16060: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `str`, found `Any | str | int | float | list[dict[str, Any]]`
tests/tools/test_stage2_hook_gateway_bootstrap_state.py:111: [no-matching-overload] no-matching-overload: No overload of function `run` matches arguments
hermes_cli/config.py:5103: [unresolved-attribute] unresolved-attribute: Attribute `items` is not defined on `int`, `str`, `list[Unknown]`, `float`, `None` in union `Unknown | int | str | ... omitted 16 union elements`
tests/cron/test_suggestions.py:213: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["monitor"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
gateway/run.py:16060: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `str | None`, found `Any | str | int | float | list[dict[str, Any]]`
tools/browser_tool.py:1196: [invalid-argument-type] invalid-argument-type: Argument to constructor `int.__new__` is incorrect: Expected `str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc`, found `Unknown | int | str | ... omitted 16 union elements`
gateway/run.py:16060: [invalid-argument-type] invalid-argument-type: Argument to bound method `AIAgent.run_conversation` is incorrect: Expected `int | float | None`, found `Any | str | int | float | list[dict[str, Any]]`
tests/tools/test_stage2_hook_install_method_stamp.py:27: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/run_agent/test_in_place_compaction.py:253: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str`, `list[Unknown]`, `list[str]`, `None`, `int`, `float` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/tools/test_stage2_hook_immutable_install.py:12: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_stage2_hook_log_dir_seed.py:20: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_stage2_hook_toplevel_chown.py:109: [no-matching-overload] no-matching-overload: No overload of function `run` matches arguments
tests/tools/test_web_providers.py:219: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["extract_backend"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/cli/test_reasoning_command.py:552: [invalid-argument-type] invalid-argument-type: Argument to bound method `TestCase.assertIn` is incorrect: Expected `Iterable[Any] | Container[Any]`, found `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/hermes_cli/test_kanban_core_functionality.py:3369: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str`, `list[Unknown]`, `list[str]`, `None`, `int`, `float` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/cli/test_resume_display.py:716: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["resume_display"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/tools/test_browser_lightpanda.py:242: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["engine"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/hermes_cli/test_aux_config.py:54: [unresolved-attribute] unresolved-attribute: Attribute `keys` is not defined on `str`, `list[Unknown]`, `list[str]`, `None`, `int`, `float` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/tools/test_refresh_agent_mcp_tools.py:257: [invalid-argument-type] invalid-argument-type: Argument to constructor `float.__new__` is incorrect: Expected `str | Buffer | SupportsFloat | SupportsIndex`, found `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 36 union elements`
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
tui_gateway/server.py:3830: [invalid-argument-type] invalid-argument-type: Argument to `AIAgent.__init__` is incorrect: Expected `str`, found `Any | None`
... and 37 more
Unchanged: 5979 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
The fork's contributor-check (check-attribution) requires every non-merge commit author email in the PR range to be present in AUTHOR_MAP. The 470-commit catch-up introduced 2 unmapped upstream author emails (one a second email for existing contributor DavidMetcalfe, one for lEWFkRAD). Added both; verified the attribution check now reports 0 missing and AUTHOR_MAP still parses with no new conflicting-username duplicates.
Upstream catch-up — owner review required
Resolves the #562 escalation: the fork had fallen 470 commits behind upstream
nousresearch/hermes-agentwhile carrying 301 commits ahead (fork-intentional divergence). This branch mergesupstream/maininto a branch offorigin/main, resolves every conflict (preserving fork evolution intent, taking upstream's genuine fixes), and fixes the CI/governance breakage the catch-up surfaced.Commits on this branch
merge— the--no-ffcatch-up merge (18 conflicts resolved)fix(codex)— reconcile cache_key parallel-impl (preserve fork explicit-key design)fix(codex)— address cross-review (sync docstring + restore dropped fork test)fix(ci)— drop upstream reusabledocker:job (fork uses standalone docker.yml)chore(release)— map 2 new upstream contributor emailsCOUNTS
6cc07b6cd0344e63340aa003a5e90a5bdefe14c0CORE RESOLUTIONS
agent/conversation_loop.py_impltelemetry split kept; upstream's newmoa_configverified threaded (AIAgent forwarder → wrapper →_impl, all 9 params) — no param-drop.agent/retry_utils.pyextract_retry_after_seconds+ upstream Z.AI overload backoff; call siteconversation_loop.py:4462present (upstream fix fully integrated).agent/transports/codex.pycron/scheduler.pycache_key="cron_<id>"thread preserved.hermes_cli/config.pyload_config()dedupe;pre_update_backup/DEFAULT_SOUL_MDrelocated, not lost.tools/mcp_tool.py,tools/terminal_tool.pyAuto-merged but audited (correction-learning feature):
agent/turn_finalizer.py(upstream addedclose_interrupted_tool_sequence#48879 before correction block;decide_correction_review/block_durable_writesintact) andrun_agent.py(moa_configforwarder threaded;_record_turn_correction/CorrectionLearner/clear_interruptuntouched).cache_key(needs owner verification)Fork and upstream independently solved cron prompt-cache coldness:
cache_keythreaded cron → Agent → chat_completion_helpers → transport (cron passescron_<id>); interactive falls back tosession_id; headers usecache_key. (Encoded by fork tests.)_content_cache_key(); interactive →pck_hash; headers usesession_id.rerereauto-unioned both, silently changing the fork's tested interactive behavior, and the auto-merged test file unioned two contradictory suites (can't both pass). Resolution: kept the fork's intentional, multi-file, tested explicit-key design; removed dead_content_cache_key+ imports; dropped the 2 upstream content-addressing tests; reconciled the shared xAI test; restored a fork test the auto-merge had dropped; fixed a stale docstring. Both solve cron equally — nothing lost. Owner may prefer upstream's automatic content-addressing long-term.CORRECTION-FEATURE INTACT? Yes.
Upstream only added code to
turn_finalizer.py/run_agent.py(auto-merged); correction-learning wiring preserved.correction_learning.py,correction_review.py,background_review.py,corrections_cli.pyuntouched by upstream.NON-CORE + CI/governance fixes
docker-publish.yml→docker.ymlrename adopted.scripts/release.py→ union ofAUTHOR_MAP(no new conflicting-username dups; 4 pre-existing dups identical in both parents). Plus 2 new upstream contributor emails added so the fork'scheck-attributionpasses.model-catalog.json→ theirs (merge=theirs); byte-identical to upstream.ci.ymlfix: the auto-merge pulled in upstream's reusabledocker:job (uses: ./.github/workflows/docker.yml), but the fork keeps docker.yml as a standalone (non-workflow_call) workflow → GitHub rejected the whole CI workflow ("workflow file issue", 0 jobs ran). Dropped the reusabledocker:job (not a required check) to match fork architecture; verified all otheruses:targets are valid reusable workflows.CROSS-REVIEW (agy / Gemini)
Codex decision "highly defensible" —
session_idenables monotonic per-turn prefix caching a static content-hash can't. Surfaced 2 real issues (stale docstring; a fork test dropped by the auto-merge) — both fixed. Its other 2 flags (NameErrorin_run_conversation_impl;adaptive_rate_limit_backoffdead) verified false against the real tree (advisor read a divergent snapshot).TEST RESULTS — CI green
check-attribution: PASS (after the AUTHOR_MAP fix).origin/main): 2test_mcp_toolcoroutine-gc tests (fail identically on fork main;_run_on_mcp_loopbyte-identical) and 2 ordering-pollution cases that pass in isolation.CI ROLLUP
🟢 Green: CI workflow (all test slices, lints, typescript, docs, supply-chain, osv, docker-lint, uv.lock, contributors), Nix.
🔴 GitGuardian Security Checks — false positive: flags a fake
Authorization: Bearertoken in an upstream test fixture whose test asserts the token is redacted (token not in sanitized). From upstream's commit history (GitGuardian green on other fork PRs), inherent to catching up to upstream; owner resolves on the GitGuardian dashboard. Not fixable without diverging from upstream's tests.MERGE-READINESS
Core merge is sound (param threading, correction wiring, symbol integrity verified; cross-reviewed; full CI test suite green). Blockers before merge: (1) owner sign-off on the codex
cache_keyparallel-impl decision; (2) triage/allow the GitGuardian false positive. Do not merge on this PR's say-so.Refs #562.