fix(tui_gateway): register shell hooks in _make_agent so they dispatch interactively - #67084
Conversation
…h interactively Shell hooks configured under the hooks: block in config.yaml were invisible to the tui_gateway daemon process: register_from_config only ran in the transient hermes chat launcher, but PluginManager._hooks is per-process, so the long-lived daemon that actually runs the agent conversation never saw the configured callbacks and configured hooks silently never fired. Add a small helper in tui_gateway.server that loads config.yaml and calls register_from_config, and invoke it inside _make_agent under the existing try/except pattern used by neighboring init steps. register_from_config is idempotent so launcher + gateway calls dedupe to a single registration. HERMES_SAFE_MODE=1 is honored inside register_from_config and turns this into a no-op. Fixes NousResearch#67053
teknium1
left a comment
There was a problem hiding this comment.
Thanks for covering the TUI-process registration gap. The premise is confirmed on current main: tui_gateway/server.py:4808-4829 builds an agent without shell-hook registration, while agent/conversation_loop.py:362-374 dispatches on_session_start through the process-local plugin manager.
Problems
tests/tui_gateway/test_issue_67053_shell_hook_registration.py:79invokes_register_shell_hooks_from_config()directly. It does not prove that_make_agent()calls it at PRtui_gateway/server.py:4882; removing that production call would leave the added tests green.
Suggested changes
- Add a factory-level regression using the existing
_make_agent()test setup attests/test_tui_gateway_server.py:8796, asserting that an agent build invokes shell-hook registration with the loaded config. Retain the helper idempotence and safe-mode coverage.
The patch otherwise follows the established register_from_config contract in agent/shell_hooks.py:203-284, and applies cleanly to current main. Automated hermes-sweeper review.
| "_register_shell_hooks_from_config" | ||
| ) | ||
|
|
||
| server._register_shell_hooks_from_config() |
There was a problem hiding this comment.
This verifies the helper but not its production wiring. Please add a test that calls _make_agent() through its existing factory seams and asserts it invokes this registration path; otherwise removal of the _make_agent call remains undetected.
|
Re-confirming the relationship to #48770 for maintainer clarity (autopilot housekeeping note, not a close candidate): Both PRs add the same root-cause fix (call
The triage bot's |
Problem
Shell hooks configured under
hooks:in~/.hermes/config.yamlsilently never fired during interactivehermes chatsessions.hermes hooks doctor/list/testreported healthy, but the live run dispatched only the built-in plugin callbacks — every configuredpre_llm_call/on_session_start/ etc. hook was a no-op.Root cause
register_from_config()is called fromhermes_cli/main.pyandcli.py(the transient launcher process), but not from anywhere in thetui_gatewaydaemon that actually runs the agent conversation.PluginManager._hooksis an in-memory per-process dict, so the launcher's registrations are invisible to the gateway. The daemon builds agents throughtui_gateway/server.py::_make_agentwithout ever wiring hooks up.hermes hooks testis misleading here — it invokes the configured script directly viashell_hooks.run_once(), bypassing the plugin dispatcher entirely. The script runs fine; real events never reach it.Fix
Add a small helper
_register_shell_hooks_from_config()intui_gateway.serverthat loadsconfig.yamland callsregister_from_config(), and invoke it inside_make_agentunder the existingtry/exceptpattern used by neighboring init steps (e.g.wait_for_mcp_discovery).register_from_configis idempotent (deduped on(event, matcher, command)), so launcher + gateway calls register the hook exactly once.HERMES_SAFE_MODE=1is honored insideregister_from_configand turns this into a no-op.This mirrors the issue reporter's verified fix (see #67053 root cause analysis) — placement in
_make_agentwas chosen overtui_gateway/entry.py:mainso the gateway's two agent-construction paths (inline and compute-host) both pick it up.Reproduction
~/.hermes/config.yaml, e.g.:hook.sh:printf '{}\n')hermes hooks allow /abs/path/to/hook.shhermes --accept-hooks chat→ send one messagePluginManager._hooksin the daemon process; script never executes.register_from_configruns in_make_agent, callback is wired up, hook fires.Verification
Three targeted regression tests in
tests/tui_gateway/test_issue_67053_shell_hook_registration.py:test_register_shell_hooks_helper_exists_and_registers— calls the patched helper directly, asserts the configured hook shows up onPluginManager._hooks. Fails onmain(helper doesn't exist); passes with the fix.test_register_shell_hooks_helper_is_idempotent— two calls register exactly one callback, confirming launcher + gateway calls dedupe safely.test_safe_mode_skips_registration—HERMES_SAFE_MODE=1short-circuits the helper, matching the safety guaranteeregister_from_configalready provides.Iron Rule 1 verified via the stash three-step dance: stash the source fix →
test_register_shell_hooks_helper_exists_and_registersfails onmain; pop the fix → all three tests pass.Out of scope
register_from_configonconfig.yamlreload events — the current call-on-_make_agentmodel is enough for the common case (one_make_agentper session). Config-reload mid-session is a separate concern.Closes #67053