Skip to content

sync: catch up ~469 commits from upstream (owner review) — resolves #562 escalation#576

Merged
Lexus2016 merged 475 commits into
mainfrom
sync/upstream-2026-06-27-review
Jun 27, 2026
Merged

sync: catch up ~469 commits from upstream (owner review) — resolves #562 escalation#576
Lexus2016 merged 475 commits into
mainfrom
sync/upstream-2026-06-27-review

Conversation

@Lexus2016

@Lexus2016 Lexus2016 commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Upstream catch-up — owner review required

Resolves the #562 escalation: the fork had fallen 470 commits behind upstream nousresearch/hermes-agent while carrying 301 commits ahead (fork-intentional divergence). This branch merges upstream/main into a branch off origin/main, resolves every conflict (preserving fork evolution intent, taking upstream's genuine fixes), and fixes the CI/governance breakage the catch-up surfaced.

[REVIEW BEFORE MERGE] One core resolution is a deliberate parallel-implementation judgment call (codex cache_key) — flagged below for owner verification. Do not auto-merge.

Commits on this branch

  1. merge — the --no-ff catch-up merge (18 conflicts resolved)
  2. fix(codex) — reconcile cache_key parallel-impl (preserve fork explicit-key design)
  3. fix(codex) — address cross-review (sync docstring + restore dropped fork test)
  4. fix(ci) — drop upstream reusable docker: job (fork uses standalone docker.yml)
  5. chore(release) — map 2 new upstream contributor emails

COUNTS

  • Behind: 470 → 0 after merge. Ahead: 301. Merge-base: 6cc07b6cd0344e63340aa003a5e90a5bdefe14c0
  • Conflicts: 18 files — 7 core code, 5 tests, 5 CI workflows, 1 contributor map.

CORE RESOLUTIONS

File Resolution Rationale
agent/conversation_loop.py union Fork's wrapper/_impl telemetry split kept; upstream's new moa_config verified threaded (AIAgent forwarder → wrapper → _impl, all 9 params) — no param-drop.
agent/retry_utils.py union Fork extract_retry_after_seconds + upstream Z.AI overload backoff; call site conversation_loop.py:4462 present (upstream fix fully integrated).
agent/transports/codex.py fork-preserving (FLAGGED) Parallel-impl collision — see flag.
cron/scheduler.py union All 46 fork + 48 upstream symbols (53 unique); cron cache_key="cron_<id>" thread preserved.
hermes_cli/config.py upstream refactor load_config() dedupe; pre_update_backup/DEFAULT_SOUL_MD relocated, not lost.
tools/mcp_tool.py, tools/terminal_tool.py union Fork divergence + upstream changes; no symbol dropped/duplicated.

Auto-merged but audited (correction-learning feature): agent/turn_finalizer.py (upstream added close_interrupted_tool_sequence #48879 before correction block; decide_correction_review/block_durable_writes intact) and run_agent.py (moa_config forwarder threaded; _record_turn_correction/CorrectionLearner/clear_interrupt untouched).

⚠️ FLAGGED — codex cache_key (needs owner verification)

Fork and upstream independently solved cron prompt-cache coldness:

  • fork: explicit cache_key threaded cron → Agent → chat_completion_helpers → transport (cron passes cron_<id>); interactive falls back to session_id; headers use cache_key. (Encoded by fork tests.)
  • upstream: content-address static prefix via _content_cache_key(); interactive → pck_ hash; headers use session_id.

rerere auto-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.py untouched by upstream.

NON-CORE + CI/governance fixes

  • 5 CI workflowsours (fork's newer pinned action SHAs + PR-gating). Upstream's docker-publish.ymldocker.yml rename adopted.
  • scripts/release.pyunion of AUTHOR_MAP (no new conflicting-username dups; 4 pre-existing dups identical in both parents). Plus 2 new upstream contributor emails added so the fork's check-attribution passes.
  • model-catalog.jsontheirs (merge=theirs); byte-identical to upstream.
  • ci.yml fix: the auto-merge pulled in upstream's reusable docker: 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 reusable docker: job (not a required check) to match fork architecture; verified all other uses: targets are valid reusable workflows.

CROSS-REVIEW (agy / Gemini)

Codex decision "highly defensible"session_id enables 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 (NameError in _run_conversation_impl; adaptive_rate_limit_backoff dead) verified false against the real tree (advisor read a divergent snapshot).

TEST RESULTS — CI green

  • All 8 Python test-slice jobs + e2e: PASS on CI (run 28282649155, identical test code). All TypeScript, ruff (×3), Docs Site, OSV, supply-chain, uv.lock, docker-lint: PASS. check-attribution: PASS (after the AUTHOR_MAP fix).
  • Local targeted suite (every conflicted/resolved file + correction feature): 1312 passed, 0 merge-caused failures. Pre-existing/not-merge-caused (verified vs origin/main): 2 test_mcp_tool coroutine-gc tests (fail identically on fork main; _run_on_mcp_loop byte-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 Checksfalse positive: flags a fake Authorization: Bearer token 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_key parallel-impl decision; (2) triage/allow the GitGuardian false positive. Do not merge on this PR's say-so.

Refs #562.

teknium1 and others added 30 commits June 25, 2026 00:17
…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.
ethernet8023 and others added 22 commits June 26, 2026 19:15
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

gitguardian Bot commented Jun 27, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
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
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. 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


🦉 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.
@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: sync/upstream-2026-06-27-review 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: 11805 on HEAD, 11427 on base (🆕 +378)

🆕 New issues (241):

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.
@Lexus2016 Lexus2016 merged commit ffff7e6 into main Jun 27, 2026
38 of 39 checks passed
@Lexus2016 Lexus2016 deleted the sync/upstream-2026-06-27-review branch June 27, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.