Skip to content

feat: shell hooks — wire shell scripts as Hermes hook callbacks (PR #13143)#13296

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-eaac5e6d
Apr 21, 2026
Merged

feat: shell hooks — wire shell scripts as Hermes hook callbacks (PR #13143)#13296
teknium1 merged 1 commit into
mainfrom
hermes/hermes-eaac5e6d

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Shell hooks let users declare shell scripts in config.yaml that fire on plugin-hook events. Scripts receive JSON on stdin and can block tool calls or inject context.

Cherry-picked from PR #13143 by @pefontana onto current main.

Changes

  • agent/shell_hooks.py (831 lines): Core engine — config parsing, consent model, subprocess execution, PluginManager integration
  • hermes_cli/hooks.py (385 lines): CLI subcommands — list/test/revoke/doctor
  • hermes_cli/main.py: --accept-hooks flag propagation, agent-command gating for hook registration
  • gateway/run.py: +33 lines — defensive hook registration at gateway startup
  • hermes_cli/plugins.py: +1 line — adds subagent_stop to VALID_HOOKS
  • tools/delegate_tool.py: +45 lines — fires subagent_stop hook after children complete
  • hermes_cli/config.py: hooks: {} and hooks_auto_accept: false defaults
  • cli-config.yaml.example: Documented hooks config block
  • website/docs/user-guide/features/hooks.md: Full documentation with wire protocol, examples, comparison table
  • 5 test files (96 tests passing): consent flow, allowlist concurrency, CLI subcommands, subprocess execution, matcher filtering

Validation

Test suite Result
tests/agent/test_shell_hooks.py passed
tests/agent/test_shell_hooks_consent.py passed
tests/agent/test_subagent_stop_hook.py passed
tests/hermes_cli/test_hooks_cli.py passed
tests/hermes_cli/test_argparse_flag_propagation.py passed
Total 96 passed

Users can declare shell scripts in config.yaml under a hooks: block that
fire on plugin-hook events (pre_tool_call, post_tool_call, pre_llm_call,
subagent_stop, etc). Scripts receive JSON on stdin, can return JSON on
stdout to block tool calls or inject context pre-LLM.

Key design:
- Registers closures on existing PluginManager._hooks dict — zero changes
  to invoke_hook() call sites
- subprocess.run(shell=False) via shlex.split — no shell injection
- First-use consent per (event, command) pair, persisted to allowlist JSON
- Bypass via --accept-hooks, HERMES_ACCEPT_HOOKS=1, or hooks_auto_accept
- hermes hooks list/test/revoke/doctor CLI subcommands
- Adds subagent_stop hook event fired after delegate_task children exit
- Claude Code compatible response shapes accepted

Cherry-picked from PR #13143 by @pefontana.
@teknium1 teknium1 merged commit 3988c3c into main Apr 21, 2026
7 of 8 checks passed
@teknium1 teknium1 deleted the hermes/hermes-eaac5e6d branch April 21, 2026 03:53
@teknium1 teknium1 mentioned this pull request Apr 21, 2026
9 tasks
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.

2 participants