Commit graph

9945 commits

Author SHA1 Message Date
Jeeves Assistant
080bb83746 test(homeassistant): prevent unit tests from calling live instances
Two tests made real HTTP calls to homeassistant.local:8123 — on a LAN
with an actual Home Assistant instance they could turn on real lights,
and otherwise burned ~10s in network timeouts. Replace with AsyncMock at
_async_call_service and assert the exact production call signature
(domain, service, entity_id, data). 35 pass in ~0.2s.

Salvaged from PR #72634 by @jeeves-assistant.

Co-authored-by: Jeeves Assistant <jeevesassistant00@gmail.com>
2026-07-29 21:30:53 -07:00
Tony (eazye19)
9e9c220608 chore(tests): drop accidental temp guard probe file
tests/test_zz_guard_probe_tmp.py was a throwaway verification probe that
was swept into the PR #43299 salvage commit by mistake (and one of its
shell=True cases fails by design of the probe). The durable regression
coverage lives in tests/test_live_system_guard.py.
2026-07-29 21:30:53 -07:00
sunwz1115
230a2c273e test: harden yolo and kanban signal tests on macOS
Autouse fixture also resets approval_module._YOLO_MODE_FROZEN so a
HERMES_YOLO_MODE=1 host env can't poison every case (the one
startup-frozen test still patches it back explicitly). Adds the darwin
'ps -o stat=' zombie branch to _is_alive_like_dispatcher, mirroring
production hermes_cli/kanban_db.py — a no-op on Linux.

Salvaged from PR #34069 by @sunwz1115.

Co-authored-by: sunwz1115 <192549904+sunwz1115@users.noreply.github.com>
2026-07-29 21:30:53 -07:00
SmokeDev
3e7a11ca2e fix(test-runner): native Windows venv probe + glyph-safe stdio
Two Windows fixes for the canonical test runner, salvaged from #66496:

- scripts/run_tests.sh: probe the native Windows venv layout
  (Scripts/activate → Scripts/python.exe) alongside bin/activate,
  adapted to main's pytest-import-guarded loop with SKIPPED_VENVS
  reporting. Without it a python -m venv / uv venv on Git Bash/MSYS is
  never found and the runner refuses to start.
- scripts/run_tests_parallel.py: _make_stdio_glyph_safe() reconfigures
  stdout/stderr to UTF-8 (errors=replace fallback) so the ✓/✗ progress
  glyphs cannot crash a cp1252 console when the runner is invoked
  directly (run_tests.sh's PYTHONUTF8=1 only covers the wrapped path).
  No-op on UTF-8 stdio. Ships 3 OS-independent cp1252 tests plus
  encoding=utf-8 in the runner-subprocess assertions.

Dropped from the original PR: the USERPROFILE/HOMEDRIVE/HOMEPATH/
SYSTEMROOT env-forwarding hunk — main's WIN_ENV loop already forwards
a superset (#67385/#70813).
2026-07-29 21:30:53 -07:00
Jeff Watts
0860b9804f test(tui_gateway): pin goal-command config home against collection-time _hermes_home freeze
Sibling files (test_billing_rpc etc.) import tui_gateway.server at
collection time, freezing module-level _hermes_home = get_hermes_home()
(server.py:54) to the developer's real home before conftest isolation
runs. _load_cfg() then reads the REAL ~/.hermes/config.yaml — e.g. a
local MoA preset — instead of what _write_moa_config wrote.

The server fixture now monkeypatches _hermes_home to the isolated
HERMES_HOME and resets the mtime-keyed cfg cache (_cfg_cache/_cfg_mtime/
_cfg_path); monkeypatch restores originals on teardown.

Complementary to the #57066 autouse teardown, which only restores the
cfg cache to its pre-test value and never re-points _hermes_home.

Combined tests/tui_gateway + tests/test_tui_gateway_server.py:
792 passed.

Salvaged from PR #63981 by @lEWFkRAD.
2026-07-29 21:30:53 -07:00
Tony (eazye19)
7216ca19a6 fix(tests): live-system guard treats only argv[0] as the executable
Argv-list commands now scan only argv[0] against _PROCESS_KILLERS, ending
false positives like ['cat', '.../skill'] ('skill' is a real util-linux
binary name). Wrapper executables (sh/bash/env/nohup/timeout/sudo/xargs/…)
keep full-token scanning so ['bash', '-c', 'pkill …'] and
['env', …, 'pkill', …] stay blocked; string commands are unchanged.
Adds tests/test_live_system_guard.py pinning both directions.

Salvaged from PR #43299 by @eazye19.

Co-authored-by: Tony (eazye19) <support@captureclient.net>
2026-07-29 21:30:53 -07:00
Christopher-Schulze
00cd9b2b3a test(fal): pin fal_common behavioral contracts
Contract tests for tools/fal_common.py: queue-URL normalization
(trailing slash / whitespace / empty-raises), _extract_http_status
response-vs-exc precedence and non-int rejection, the
_ManagedFalSyncClient RuntimeError guards against fal_client private
API drift, and submit()'s queue-URL + POST/json/timeout wiring.

Trimmed on landing: test_non_string_coerced_to_string (pins an
implementation accident, not a contract) per the coverage-padding
policy in AGENTS.md.

Salvaged from #52166.
2026-07-29 21:30:53 -07:00
Jeff Watts
f708a85d2e test(tui_gateway): make the tui gateway suite order-independent
Cross-file leaks made tests/tui_gateway + tests/test_tui_gateway_server.py
fail when run in one process (issue #57068):

- conftest: _hermetic_environment now re-pins hermes_state.DEFAULT_DB_PATH
  to the fake home so no test ever touches the developer's real state.db
- conftest: new autouse _reset_tui_gateway_server_state snapshots/restores
  _methods, cfg cache, db handle and _real_stdout, and tears down leftover
  sessions via the production _close_session_by_id(..., end_reason=
  "test_cleanup") boundary
- per-file fixtures scope patch.dict(sys.modules) to the import only
- drop reload()-based teardowns that duplicated atexit hooks

Conflict resolution vs main: kept main's mod._live_transports.clear() in
the test_protocol.py server fixture alongside the snapshot/restore logic.

Combined run now 792 passed / 0 failed.

Salvaged from PR #57066 by @lEWFkRAD. Fixes #57068.
2026-07-29 21:30:53 -07:00
Kyzcreig
9c28600e77 test: prove concurrency with barriers/witnesses instead of wall-clock bounds
Replace OS-scheduler-dependent elapsed-time ceilings with deterministic
concurrency proofs in three tests:

- test_context_refs_concurrent: asyncio.Barrier rendezvous — all 3 URL
  fetches must be in flight simultaneously before any returns.
- test_memory_boundary_commit: positive non-blocking witness — the
  provider call list must still be empty when the async commit returns.
- test_mem0_v3 slow-prefetch: threading.Event park/release — prefetch
  must return while the backend search is still parked.

The tests/tools/test_mcp_tool.py hunk from the original PR is dropped:
main no longer carries the 'elapsed < 2.5' assertion it targeted
(superseded by a delay-relative bound).

Salvaged from #71913.
2026-07-29 21:30:53 -07:00
fcavalcantirj
f04fd1e7ad fix(update): test runs never mutate the live checkout — pytest-guard the marker and repair paths
Guard the .lazy-refresh-incomplete marker writer (update_cmd), launch-time
recovery (main.py), and _early_recovery repair paths behind a two-condition
check: running under pytest AND the target is this live checkout. Sandboxed
tmp_path tests still exercise the real code paths.

Salvaged from PR #72002 by @fcavalcantirj. Fixes #72000.

Co-authored-by: fcavalcantirj <felipe.cavalcanti.rj@gmail.com>
2026-07-29 21:30:53 -07:00
Jakub Wolniewicz
3233de9a21 fix(tests): preserve config module identity in backup tests
(cherry picked from commit 1ac105ea50)
2026-07-29 21:30:53 -07:00
Teknium
2d40494247 fix(tests): tolerate mid-import kanban_db in the write-guard sys.modules probe
The guard's sys.modules.get() can observe hermes_cli.kanban_db while a
lazy import is still executing on a fixture boundary — the partially
initialized module has no .connect yet (AttributeError flake in the
full-suite verification run, 1/2460 files). A half-imported module has
no callers to guard; skip this round and let the next fixture patch the
completed module.
2026-07-29 19:53:17 -07:00
joelbrilliant
ef1b06d853 fix(tests): stop the suite writing into the operator's real Hermes logs
hermes_cli/main.py calls setup_logging() at module scope. That resolves
get_hermes_home() and attaches rotating file handlers to the ROOT logger via
a QueueHandler. So merely importing it - which many test modules do, directly
or transitively - points the whole pytest session's logging at
<HERMES_HOME>/logs/agent.log and errors.log.

The _isolate_env fixture already sandboxes HERMES_HOME, but fixtures run
after collection has imported the test modules, and by then the handler holds
an absolute path to the real file. Verified by importing hermes_cli.main in a
clean interpreter and walking the queue listener: both handlers pointed at the
developer's own ~/.hermes/logs/.

Measured on a live install: 126 warnings in a personal agent.log came from
test runs rather than the running gateway - phantom 'FakeTree' Discord
registration failures and 'rejected invalid API key' entries whose paths only
exist in tests/gateway/test_api_server_runs.py. That noise makes genuine
warnings hard to find exactly when someone is debugging.

conftest is imported before any test module, so sandboxing HERMES_HOME there
closes the window. The per-test fixture still applies afterwards.

Also fixes 4 pre-existing failures: tests/gateway/test_channel_directory.py
TestBuildFromSessions was reading the operator's real sessions data for the
same reason.

Full gateway+tools suites: 66 failures on clean origin/main, 62 with this
change, 0 new. The regression guard asserts the value captured AT conftest
import - reading os.environ inside a test passes even with the fix removed,
because the per-test fixture has sandboxed it by then.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:53:17 -07:00
mehmetkr-31
dda289933d test(cli): fix order-dependent test_resume_quiet_stderr flake at the source
test_session_not_found_goes_to_stdout_in_full_mode passes in isolation but
fails in a full tests/cli run. Two independent leaks from the same neighbor
test conspire:

1. test_cli_init.py's _make_cli() reloads cli.py while prompt_toolkit is
   stubbed with MagicMocks and never reloads it back, so sys.modules['cli']
   is left with a mock _pt_print/_PT_ANSI and cli._cprint silently no-ops
   for every later test. Fixed by reloading cli once more with the real
   modules visible (try/finally).

2. prompt_toolkit's print_formatted_text caches its Output on the
   process-global default AppSession the first time it renders without an
   explicit output=. Under capsys (which swaps sys.stdout per test), the
   first CLI test to emit through _cprint locks that cache onto its own
   captured stdout, so later capsys tests read an empty buffer. Fixed with
   an autouse fixture in a new tests/cli/conftest.py that resets the cached
   output around each test.

Neither change touches production code or the flaky test's own assertion.

Related to #59358 (which addresses the same flaky test by mocking _cprint in
the assertion instead; this fixes the two underlying leaks at the source and
does not modify test_resume_quiet_stderr.py).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 19:53:17 -07:00
Teknium
7ac63975f7 test: fix restored-test regressions vs current main
- test_terminal_requirements.py: restore missing 'import pytest' (revert
  resurrected a parametrized test into a file whose pytest import was
  pruned on main)
- test_container_cwd_sanitize.py: _CONTAINER_BACKENDS pin now includes
  vercel_sandbox
2026-07-29 19:48:37 -07:00
Teknium
c770515e2b modernize re-added Vercel integrations: SDK 0.7.2, telemetry off, sibling-site wiring
- Bump vercel SDK pin 0.5.7 -> 0.7.2 (pyproject, lazy_deps) and regenerate uv.lock
- Disable the SDK's new default-on telemetry (VERCEL_TELEMETRY_DISABLED=1
  set before import, user-overridable) per the no-opt-out-telemetry policy
- Move _model_flow_ai_gateway into hermes_cli/model_setup_flows.py (god-file
  decomposition landed after the removal)
- Widen post-removal backend sets that vercel_sandbox missed: terminal_tool
  container_backend + _CONTAINER_BACKENDS, file_tools fallback set,
  env_probe._REMOTE_BACKENDS, approval._should_skip_container_guards,
  prompt_builder probe container_config
- Add terminal.vercel_runtime to config_defaults + TERMINAL_CONFIG_ENV_MAP
- Re-add vercel dependency group to nix #full variant (reverts #33773 workaround)
- Update restored tests to current contracts: upload-only credential sync-back
  (bcfc7458fa), registry-derived provider env list, parametrized backend fixture,
  drop tests superseded on main (slack wizard move #41112, nous status format)
2026-07-29 19:48:37 -07:00
Teknium
ad12df6ba4 Revert "remove Vercel AI Gateway and Vercel Sandbox (#33067)"
This reverts commit febc4cfec0.
2026-07-29 19:48:37 -07:00
Alexander Russell
cea4c3362d fix(gateway): collect quoted/spaced/home-relative MEDIA paths into the history dedup set
Salvaged from PR #73982 by Alexander Russell (@AlexxRussell) — the
collector half only. _collect_history_media_paths used only
_TOOL_MEDIA_RE, which misses quoted and spaced paths that the delivery
pipeline's extract_media grammar accepts; run text content through the
same extractor so the surviving dedup consumers (auto-append lane and
bare-path filter) see every path that could actually have been
delivered.

The PR's other halves (post-stream dedup snapshot plumbing, canonical
path comparison in _deliver_media_from_response, queued-followup
snapshot union) are moot after #74495 removed the post-stream history
filter entirely.
2026-07-29 19:11:05 -07:00
Fraser Humphries
8f4122efd2 test(gateway): cover current-turn TTS media dedup 2026-07-29 19:11:05 -07:00
Fraser Humphries
4dc9fb8008 fix(gateway): exclude current-turn media from dedup 2026-07-29 19:11:05 -07:00
Teknium
aae6e52005 test: opt install-ladder tests back into lazy installs under the hermetic gate
The HERMES_DISABLE_LAZY_INSTALLS=1 conftest gate (from #43782) correctly
blocks real mid-run pip installs suite-wide, but
TestInstallDependenciesRunner exercises the install ladder itself against
a fully mocked subprocess.run — it needs the gate open. Same
both-directions override pattern tests/tools/test_lazy_deps.py already
uses. Sibling sweep of all install_specs/_pip_install/ensurepip test
files: 274 tests green.
2026-07-29 18:55:10 -07:00
eapwrk
bd1a850fa2 fix(honcho): network-hermetic unit tests + lazy async writer start
Re-port of PR #67576:
- plugins/memory/honcho/session.py: start the async writer thread lazily on
  first enqueue via _ensure_async_writer (idempotent, lock-guarded) instead
  of eagerly in __init__; shutdown tolerates a never-started thread
- tests/honcho_plugin/conftest.py: package-wide socket guard so no honcho
  unit test can reach a live server
- tests/honcho_plugin/test_network_isolation.py: regression tests
- test_async_memory.py / test_oauth_flow.py: rebased hunks onto the pruned
  suite (pruned tests not resurrected)

Salvaged-from: #67576
Co-authored-by: eapwrk <eapwrk@gmail.com>
2026-07-29 18:55:10 -07:00
y0shualee
8d009e4f3e test: guard browser and Keychain side effects suite-wide (#35404)
Re-port of PR #35464 onto the rewritten hermetic conftest:
- autouse _neutralize_webbrowser fixture records open/open_new/open_new_tab
  and webbrowser.get() instead of launching a real browser
- autouse _neutralize_macos_keychain_creds defaults the Anthropic Keychain
  reader to None, with an opt-in allow_macos_keychain marker
- regression tests in tests/test_hermetic_side_effect_guards.py
- tests/agent/test_anthropic_keychain.py opts in via pytestmark

Salvaged-from: #35464
Co-authored-by: y0shualee <yuxiangl490@gmail.com>
2026-07-29 18:55:10 -07:00
Rod Boev
e461e86502 test(cli): remove importlib.reload seam from web_server session-token tests (#38034)
Extract _resolve_session_token() in hermes_cli/web_server.py so tests can
exercise token resolution directly instead of importlib.reload(ws), which
re-executed the whole module mid-suite (fresh FastAPI app + token) and
split module identity between test and app state.

Salvaged from PR #39038 by @rodboev (maintainer-endorsed direction);
rebased onto the rewritten test_web_server.py — dropped the PR's hunks for
test_falls_back_to_random_token's old body (test deleted in the prune,
re-added here in the PR's new form).

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-29 18:55:10 -07:00
Jasmine Naderi
7ba456e98d fix(test): fail-closed kanban write guard prevents real HERMES_HOME pollution (#69283)
Autouse conftest fixture patches kanban_db.connect to refuse writes whose
resolved DB path lands under the REAL kanban root (captured at conftest
import time, before fixtures rewire the environment). Deny-list, not
allow-list, so hermetic tests moving HERMES_HOME to sibling tempdirs are
unaffected. Lazily attaches only when hermes_cli.kanban_db is already in
sys.modules.

Salvaged from PR #69385 by @smfworks; rebased by hand onto the pruned
conftest and adapted to guard on the resolved DB path (explicit db_path
or kanban_db_path()) rather than kanban_home() alone.

Co-authored-by: Jasmine Naderi <jasmine@smfworks.com>
2026-07-29 18:55:10 -07:00
AIalliAI
74d9110a7a test(tui): scope close_race orphan-cleanup assert to own session key
test_session_create_close_race_does_not_orphan_worker asserted the
process-global len(unregistered_keys) >= 1: a leaked _build thread from
another session.create test in the same shard can append a foreign key
and falsely satisfy the assert. Scope both the wait loop and the assert
to this test's own stored_session_id, matching the own-key scoping the
no-race companion test already uses for the same flake class.

Salvaged from PR #46189 by @AIalliAI (rebased onto the pruned suite).
2026-07-29 18:55:10 -07:00
Jorkey Liu
05afea65f4 fix(session): resolve default state DB path at call time
DEFAULT_DB_PATH in hermes_state.py is computed at import time, freezing
the developer's real ~/.hermes even when a test fixture (or runtime
profile switch) later redirects HERMES_HOME. Any default SessionDB() —
e.g. gateway SessionStore — then opened the real state.db.

Add _default_db_path(): resolves get_hermes_home() fresh at call time,
while a deliberately re-pointed DEFAULT_DB_PATH (the established
monkeypatch escape hatch) still wins via an import-time snapshot
comparison, preserving existing test behavior. SessionDB.__init__ and
session_search's requirement check now use the resolver; explicit
db_path arguments are untouched.

Reimplemented from PR #11875 by @JorkeyLiu (original diff predates the
hermes_state rewrite); regression test ported and modernized.
2026-07-29 18:55:10 -07:00
Jasmine Naderi
3e54a366e7 fix(test): patch _launch_configured_cwd in completion tests for hermeticity (#70041)
tui_gateway/server.py freezes _hermes_home at import time, so
_launch_configured_cwd() reads the developer's real config.yaml even
under the per-test HERMES_HOME redirect. Any absolute terminal.cwd in
the real config made _completion_cwd() ignore monkeypatch.chdir and
broke the completion tests on a pristine checkout.

Patch _launch_configured_cwd to None in the autouse _reset_fuzzy_cache
fixture and add a regression test pinning that _completion_cwd resolves
via os.getcwd() under tests.

Salvaged from PR #70148 by @smfworks (rebased onto the pruned suite).
2026-07-29 18:55:10 -07:00
webtecnica
66c4c9c0b1 fix(tests): forward Windows location vars through the hermetic runner; patch Path.home() in hindsight _clean_env
Combines the Windows-hermeticity cluster (#67512 by @webtecnica, earliest;
#71112 by @Sanjays2402; #67196 by @anatolijlaptev1991-ctrl) into one fix:

- scripts/run_tests.sh: env -i forwarded only HOME, but native Windows
  CPython resolves Path.home() from USERPROFILE (or HOMEDRIVE+HOMEPATH),
  stdlib paths from LOCALAPPDATA/APPDATA, ssl/sockets need SYSTEMROOT,
  tempfile needs TEMP/TMP — the strip broke collection tree-wide on
  native Windows (issues #67385, #70813). Location vars (never
  credentials) are now forwarded, each only when actually set, so
  POSIX runs are byte-for-byte unchanged (probe-verified both ways).
  PYTHONUTF8=1 added for legacy-codepage consoles printing the
  runner's glyphs.
- tests/plugins/memory/test_hindsight_provider.py: _clean_env patched
  HOME only; on Windows Path.home() ignores HOME. Now patches
  Path.home directly into tmp_path (from #67196).

Not ported: #71112's guard test — it regex-reads run_tests.sh source,
which the test policy bans (never read source code in tests).

Fixes #67385. Fixes #70813.
2026-07-29 18:55:10 -07:00
Omar Baradei
2472793b1d Refresh on upstream/main: resolve conflicts (no behavior change)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 18:55:10 -07:00
Kyzcreig
bcec6c8d39 fix(test): hermetic env-detection tests — pin container/supervisor/HOME probes (#422)
The restart-routing, systemd-support, and subprocess-HOME tests asserted
branch behavior but left part of the real probe surface unmocked, so they
fail when the suite itself runs inside a container (self-hosted CI) or a
launchd-descended shell:

- /restart routing tests: the handler also consults the real /.dockerenv —
  extract the inline probe to gateway.restart.is_container_restart_context()
  (patchable seam, no behavior change) and pin it False; scrub ALL four
  supervisor env markers (ambient XPC_SERVICE_NAME on macOS flipped one).
- supports_systemd_services tests: pin shutil.which('systemctl') and
  is_container() so the test asserts the branch, not the host.
- copilot ACP real-HOME test: pin is_container() (auto mode prefers profile
  home in containers) and scrub ambient HERMES_REAL_HOME/TERMINAL_HOME_MODE.

97 tests green on macOS dev box AND inside a docker CI runner container.

Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
2026-07-29 18:55:10 -07:00
jethac
2c37b1af25 test(approval): event-based waits for blocking-approval E2E polls (PR #63522 port)
Manual port of @jethac's #63522 onto the pruned tree: 3 of the original
6 fixed-budget poll sites survive (3-concurrent-agents wait, session-A/B
routing wait, two-session queue wait). Replaced each 2.5-5s hard-ceiling
poll loop with the PR's _wait_until(predicate) helper — generous 30s
ceiling reached only on genuine failure, instant return on the green
path, assert with message instead of silent fall-through.

Dropped: the 3 hunks targeting pruned code, and the unrelated
scripts/release.py mailmap hunk (AUTHOR_MAP is frozen; contributor
mapping handled via contributors/emails/).
2026-07-29 18:55:10 -07:00
Teknium
67435c9a02 fix(tests): restore original module identities after vision-routing reloads (#61597)
_fresh_modules() in test_vision_routing_31179.py deleted agent.image_routing
(+siblings) from sys.modules and never restored the ORIGINAL modules — the
reloaded copies leaked. Any later same-process test holding function refs to
the original module (test_image_routing.py) then patched the reloaded copy
via mock.patch string targets, making patches invisible: order-dependent
failures (repro: 31179 file first → 2 failures; reversed → green).

Autouse fixture now snapshots the affected sys.modules entries and restores
them on teardown. Both orders + solo runs verified green.

Masked by the per-file-isolated canonical runner; bites anyone running
pytest tests/agent/ directly.
2026-07-29 18:55:10 -07:00
Fangliquan
483b9e3328 test(tests): assert SessionDB timeout without wall-clock
Replace elapsed-time checks with captured Future.result(timeout=...) values so the hang regression stays deterministic under parallel CI load.
2026-07-29 18:55:10 -07:00
Teknium
646761c783 fix(gateway): widen explicit-MEDIA resend fix to the streaming path + log bare-path suppression
Companion to the cherry-picked #74158 fix (non-streaming path):

- gateway/run.py: remove the identical history-dedup filter from
  _deliver_media_from_response — the post-stream rescan is explicit-only
  by design (#20834), so every MEDIA tag it finds is a deliberate
  attachment request; drop the now-unused history_media_paths parameter
  and its call-site plumbing.
- gateway/platforms/base.py: log suppressed bare local file paths on the
  surviving local_files history dedup (#73771 observability ask).
- tests: focused regression file covering explicit resend delivery on
  both lanes, current-turn tool-echo poisoning, surviving bare-path
  dedup + its log line, and the upstream auto-append dedup invariant.

Fixes #73771
2026-07-29 18:33:38 -07:00
Teknium
adf217b584 fix(cli): sweep aged venv.stale.runtime-* backups on hermes update
Follow-up to the salvaged success-path removal: installs that already
repaired (or predate the cleanup) still carry leaked ~1 GB parked venvs.
When the runtime probes safe, reclaim aged (>1h) stale markers next to
the live venv — age-gated to avoid racing an in-flight sibling repair,
boundary-checked via _remove_tree so symlinked names can't escape the
checkout. Also drop the now-stale 'before removing the parked venv'
user guidance in update_cmd.

Tests: success-path removal, safe-path sweep (aged removed, fresh kept).
2026-07-29 18:15:54 -07:00
Teknium
75528cd26a fix(state): reconcile salvaged WAL fixes with current main
- apply_database_pragmas: journal_mode ownership stays with
  apply_wal_with_fallback/resolve_journal_mode (single guarded owner);
  the helper now only applies wal_autocheckpoint / journal_size_limit,
  via load_config_readonly (hot-path safe).
- Silent-refusal WAL success path re-applies the macOS
  checkpoint_fullfsync barrier and synchronous=FULL enforcement.
- Test doubles updated for connect_tracked's factory kwarg and the
  WAL-reset vulnerability gate (fixed-SQLite assumption made explicit).
2026-07-29 18:13:09 -07:00
Lavya Tandel
74d6cc2209 fix(config): respect database.journal_mode from config.yaml
Config-driven `journal_mode`, `wal_autocheckpoint`, and `journal_size_limit`
are now honored in `SessionDB` init. Users running SQLite on NFS/SMB or
with custom tuning had no supported config surface; values were hardcoded
at connection time.

What
- New `apply_database_pragmas()` in `hermes_state.py`
- Reads nested `database:` keys from `config.yaml` via existing `cfg_get`/`load_config`
- Called after `apply_wal_with_fallback()` in `SessionDB._connect_and_init()`

Fix
- Adds optional PRAGMA switches for journal_mode, wal_autocheckpoint, journal_size_limit
- On Darwin; Windows keeps DELETE unless config explicitly requests WAL

Runtime Proof
$ /opt/homebrew/bin/pytest tests/test_hermes_state.py::TestApplyDatabasePragmas -q
3 passed in 0.72s

Regression Checks
- Full `tests/test_hermes_state.py`: 306 passed in 11.32s
2026-07-29 18:13:09 -07:00
Teknium
5567846006 fix(state): retry transient disk I/O errors before WAL fallback
Salvages #55322 (ZFS 'disk i/o error' marker) without regressing the
Bug D transient-EIO protection from 5c49cd0ed0. 'disk i/o error' is
ambiguous: deterministic on ZFS/APFS-CoW SHM corruption (#55305,
#71498) but often a one-shot transient (page-cache pressure, lock
contention). A blind marker match re-introduced the mixed-journal-mode
corruption pattern; a blind re-raise wedged state.db on ZFS.

Disambiguate: retry the WAL pragma twice with a short backoff. A
transient EIO clears and WAL proceeds; a deterministic failure keeps
raising and falls through to the guarded DELETE fallback (still
refusing to downgrade a DB whose on-disk header reports WAL).

Tests: transient-EIO recovery, persistent-EIO fallback, and the
never-downgrade-WAL-on-disk guard.
2026-07-29 18:13:09 -07:00
Connor Black
144e563cdd test(state): prove silent WAL fallback in callers 2026-07-29 18:13:09 -07:00
Connor Black
0f60cdac27 fix(state): escalate silent WAL→DELETE fallback to ERROR + opt-in require_wal
The WAL→DELETE fallback on WAL-incompatible filesystems (NFS / SMB / FUSE /
the AgentFS NFS overlay) was logged at WARNING, treating a real loss of
concurrency — under the kanban dispatcher + workers a write blocks readers,
surfacing as SQLITE_BUSY — as if it were cosmetic. Escalate the deduplicated
fallback log to ERROR so the degradation is observable, not silent.

Add an opt-in require_wal=True to apply_wal_with_fallback that raises a typed
WalUnsupportedError (subclass of sqlite3.OperationalError, so existing DB-init
handlers still catch it) instead of degrading to DELETE, for callers that
mandate WAL concurrency. All four current callers keep the default
require_wal=False so NFS-homed installs keep working unchanged.

Tests: 4 new require_wal cases; WARNING→ERROR assertion updates in both
test_hermes_state_wal_fallback.py and test_kanban_db.py.
2026-07-29 18:13:09 -07:00
Connor Black
f50d80e8eb fix(state): detect silent WAL→DELETE fallback on macOS NFS / SMB (no false "wal")
apply_wal_with_fallback() only detected WAL-incompatible filesystems via a
RAISED OperationalError matched against _WAL_INCOMPAT_MARKERS. But macOS NFS,
SMB/CIFS, and overlay filesystems (e.g. AgentFS's NFS-backed mount) refuse the
WAL switch WITHOUT raising: `PRAGMA journal_mode=WAL` returns the still-effective
mode ('delete') and no exception. The code then ran `return "wal"`
unconditionally, so it:
  1. returned a false "wal" while the DB was actually in DELETE mode, and
  2. never called _log_wal_fallback_once, so the operator got ZERO signal that
     concurrency had silently degraded (reader-blocks-writer).

state.db and kanban.db share this path, so a session/kanban board DB on a
network or overlay filesystem ran in DELETE with no diagnostic.

Fix: read the row `PRAGMA journal_mode=WAL` returns and verify it is actually
'wal' instead of assuming success; on a silent no-op, emit the existing
fallback WARNING and return the true mode. The raise-based path is unchanged.

Reproduced on a real AgentFS NFS overlay (PRAGMA journal_mode=WAL returned
('delete',) with no OperationalError). Adds a regression test for the
silent-no-op shape; the 16 existing WAL-fallback tests are unchanged.
2026-07-29 18:13:09 -07:00
Jasmine Naderi
91351b7b77 fix(state): make journal mode canonical and behaviorally verified
Use database.journal_mode as the sole non-secret operator setting, preserve the vulnerable-SQLite safety gate and existing WAL databases, validate explicit DELETE results, document the active config path, and cover real SQLite openers with behavioral tests.
2026-07-29 18:13:09 -07:00
Jasmine Naderi
04ec841462 fix(state): configurable journal_mode + centralize all DB openers
Add HERMES_JOURNAL_MODE env / database.journal_mode config for
virtiofs/NFS/SMB where WAL is not crash-safe. Route 5 bypass openers
through apply_wal_with_fallback so a single setting covers every .db
(#68545).
2026-07-29 18:13:09 -07:00
Ben Barclay
d26983e485
fix(gateway): relay TTS attachments + semantic auto-thread rename on the title turn (#74482)
Two relay-lane bugs from live Discord staging testing (2026-07-29):

1. TTS audio never attached over relay (any platform, any lane).
   _history_media_paths_for_session excluded only the trailing assistant
   entry from the persisted transcript when building the delivered-media
   dedup set. The agent persists rows as it produces them, so THIS turn's
   text_to_speech tool result (media_tag JSON) was already in the
   transcript at delivery time — the fresh TTS path deduped against
   itself and extract_media's attachment was silently stripped
   (response_delivery_dropped for a MEDIA-tag-only reply; fly logs show
   the exact signature). Fix: exclude everything from the last USER
   message onward (the current turn); prior-turn dedup unchanged.
   Affects every platform adapter (native + relay) on the non-streaming
   delivery path — the streaming path passes explicit history and was
   unaffected.

2. Connector-auto-created threads never got the LLM session-title
   rename. The title fires on the FIRST exchange, whose source is the
   PARENT channel event — the thread didn't exist at ingest, so the
   Phase 4 auto-thread markers can't be present and
   _is_discord_auto_thread_lane never matches on the relay title turn
   (initial titles worked; semantic renames never happened; staging
   telemetry shows zero thread_rename ops ever sent). Fix: consume the
   connector's new send-result feedback (paired gateway-gateway PR —
   contract §SendResult thread_id/auto_thread_name, additive):
   RelayAdapter.send() caches (thread_id, initial_name) per chat
   (bounded 256), run.py's title-callback registration + rename lane
   read it back and pass initial_name as only_if_current_name so the
   human-rename-wins guard holds on the relay lane too. Native marker
   path unchanged; connectors that don't stamp the fields degrade to
   exactly the old behavior.

Tests: 3 new (send-result feedback capture, absence, bound) in
test_relay_threads.py; 3 new in test_history_media_current_turn.py
(current-turn TTS not deduped, prior-turn still deduped, no-user-row
fallback). Relay suite 144 passed.
2026-07-29 18:00:18 -07:00
Teknium
8da8a7887d fix(state): time-based write-lock patience so busy sibling processes can't destroy turns
A shared state.db is legitimately held for multi-second stretches by
sibling Hermes processes: VACUUM after auto-prune, the TRUNCATE WAL
checkpoint at close on a large WAL, offline recovery, or an older
still-running process whose FTS maintenance predates the bounded-merge
protocol (every `hermes update` leaves mixed-version processes sharing
the DB until the old ones exit).

The old retry budget was attempt-counted: 15 attempts x 20-150ms jitter
gives up after ~1.3s of waiting. Any hold longer than that surfaced as:

- append_message failing -> the conversation loop aborts the turn as
  session_persistence_failed ('No reply: the turn was stopped because
  session storage could not be written') on a perfectly healthy store;
- SessionDB() open failing -> the CLI disables persistence for the
  entire run ('Failed to initialize SessionDB ... database is locked').

Both observed in production logs on 2026-07-29 (10.8 GB state.db, 9
concurrent hermes processes, three of them pre-dating the bounded-merge
fix pull).

Changes:

- _execute_write patience is now TIME-based with two budgets: routine
  writes wait up to 20s; transcript-critical writes (append_message,
  session-row creation — the ones whose failure aborts a user turn)
  wait up to 60s. Jitter stays 20-150ms for the first 2s, then backs
  off to 250ms-1s so a long hold isn't hammered with BEGIN IMMEDIATE.
- Exhausted patience raises an error that names the actual cause
  (another process held the write lock; the database is healthy)
  instead of a bare 'database is locked' that reads like disk damage —
  and the turn-abort explainer inherits that clarity.
- SessionDB open now applies the same jittered patience to the
  locked/busy class around connect+schema-init, instead of failing the
  whole open (and disabling persistence for the run) on the first 1s
  timeout. Non-lock errors, including the malformed-schema repair
  class, propagate immediately as before.

Fixes #74478
2026-07-29 17:59:02 -07:00
Teknium
240afd0b70 fix(telegram): batch near-limit command chunks so split /queue pastes don't orphan their continuation
Telegram clients split messages above 4096 chars into multiple updates. A
long '/queue <prompt>' paste arrives as a COMMAND chunk near the limit plus
plain TEXT continuation chunk(s). _handle_command dispatched the command
chunk immediately, so the continuation landed as a separate plain message
that interrupted the running agent instead of being queued.

Near-limit (>= _SPLIT_THRESHOLD) command chunks now route through the same
text-batching pipeline used for split plain-text messages, merging the
continuation before dispatch. Short commands (/stop, /approve, ...) keep the
immediate path and are never delayed.
2026-07-29 17:22:52 -07:00
teknium1
1ec5928012 fix(managed_uv): repair vulnerable SQLite runtime in .venv installs too
repair_vulnerable_runtime() hardcoded <checkout>/venv as the live venv,
so uv-default/dev checkouts installed into .venv got 'not-applicable' on
every hermes update — no repair path ever fired, leaving state.db-class
DBs on journal_mode=DELETE forever (measured 26 ms + ~5.5 fsyncs per
append vs ~0.01 ms under WAL, ~2,600x) while the WAL fallback warning
falsely promised hermes update would repair the runtime.

- _default_live_venv(): target venv/ when it has an interpreter (managed
  layout precedence), fall back to .venv/, keep not-applicable when
  neither exists. Explicit venv_dir arg unchanged; all staging/smoke/
  cutover/rollback machinery untouched.
- Rebuilt against the pruned test suite (main's test-prune waves 1+2
  rewrote test_managed_uv.py, so this reapplies cleanly): 3 new
  TestDefaultLiveVenv tests + repair neutralized in the 6 unit tests
  whose subject is uv install/self-update mechanics — with .venv now
  probed for real, CI's own vulnerable .venv made the unmocked repair
  hook fire inside those tests and re-invoke _install_uv.

33/33 tests green on the pruned suite.
2026-07-29 17:09:10 -07:00
brooklyn!
08e428ac8b
Merge pull request #74446 from NousResearch/bb/desktop-pairing
feat(pairing): profile-correct approvals, and a desktop surface to do them from
2026-07-29 19:04:36 -05:00
Brooklyn Nicholson
3c5da5307c feat(pairing): give pairing its own change signal
The poll this page's pairing block rode was retired hours earlier by
f8e07a332, which moved Messaging onto platforms.changed. That signal is the
mtime of gateway_state.json — where the gateway persists connect/disconnect
health — and a new pairing request moves none of it. So on an event-capable
backend a pending row stayed invisible until something unrelated
reconnected, and the count badge with it.

Adds pairing.changed to the change watcher, signed off the pending/approved
ledgers across the global store and every profile's own. _rate_limits.json
is deliberately excluded: it moves on every unauthorized DM, including ones
that produce no new row, so signalling on it would refetch for nothing.

The page now refreshes platforms and pairing on their own signals rather
than one combined call, and the legacy visible-tab poll (older backends)
covers both.
2026-07-29 18:50:09 -05:00