Skip to content

fix(hermesagent): emit hooks under Hermes's native VALID_HOOKS event keys - #2100

Merged
dyoshikawa merged 1 commit into
mainfrom
resolve-issue-2036-hermesagent-hooks-fix
Jul 1, 2026
Merged

fix(hermesagent): emit hooks under Hermes's native VALID_HOOKS event keys#2100
dyoshikawa merged 1 commit into
mainfrom
resolve-issue-2036-hermesagent-hooks-fix

Conversation

@dyoshikawa

Copy link
Copy Markdown
Owner

Summary

Bug fix for #2036. hermesagent-hooks.ts was previously shipped writing the canonical hooks JSON verbatim under an INVALID key (hooks: { rulesync: <canonical JSON> } }). Hermes Agent only recognizes hooks registered under its fixed VALID_HOOKS event keys, so rulesync is not one of them — Hermes silently ignored the entire block and generated hooks NEVER fired. This is a functional-no-op bug in previously-merged code.

Root cause & fix

Fetched the primary source (raw hooks.md from NousResearch/hermes-agent) directly rather than trusting a stale issue-comment event list (which contained a fabricated pre_api_request/post_api_request pair). Confirmed the real VALID_HOOKS set and entry shape ({ matcher?, command, timeout? }, shlex.split/shell=False, timeout default 60/cap 300).

Event mapping (only documented 1:1 canonical matches; everything else intentionally left unmapped rather than guessed)

Canonical Hermes native
preToolUse pre_tool_call
postToolUse post_tool_call
preModelInvocation pre_llm_call
postModelInvocation post_llm_call
sessionStart on_session_start
sessionEnd on_session_end
subagentStart subagent_start
subagentStop subagent_stop
  • matcher emitted only on pre_tool_call/post_tool_call (dropped elsewhere with a warning).
  • Only type: "command" hooks emitted — Hermes runs shlex-split commands with no shell, so prompt/http types are unsupported and dropped with a warning.
  • Import (toRulesyncHooks) rewritten to round-trip from the real native keys instead of the old hooks.rulesync blob.
  • Merged into the shared global ~/.hermes/config.yaml, same pattern as HermesagentMcp/HermesagentPermissions.
  • hooks-processor.ts factory entry corrected: was declaring CLAUDE_HOOK_EVENTS + ["command","prompt","http"] (copy-paste artifact), now declares the real HERMESAGENT_HOOK_EVENTS + ["command"].

Deferred (secondary, out of scope)

The issue also flagged a secondary rules/nonRoot gap (nested per-directory context files). Not implemented: Hermes's progressive per-directory loading only watches for AGENTS.md/CLAUDE.md/.cursorrules in subdirectories, not nested .hermes.md; wiring nonRoot as-is would repeat this exact bug pattern (write files Hermes never reads). Left for a follow-up design.

Verification

pnpm cicheck fully green: 298 test files, 6639 tests. e2e hermesagent-hooks cases pass (7/7). One pre-existing unrelated e2e-convert.spec.ts failure verified present on unmodified main via git stash — not caused by this change.

References

Closes #2036

…keys

hermesagent-hooks.ts previously wrote the canonical hooks JSON verbatim under
an invalid key (hooks.rulesync), which Hermes Agent silently ignores because
it only runs hooks registered under its fixed VALID_HOOKS event set
(pre_tool_call, post_tool_call, pre_llm_call, post_llm_call, on_session_start,
on_session_end, subagent_start, subagent_stop, ...). The generated hooks were
therefore inert no-ops despite skills/permissions already working correctly.

Map the canonical events with a clean 1:1 Hermes equivalent (preToolUse,
postToolUse, sessionStart, sessionEnd, preModelInvocation,
postModelInvocation, subagentStart, subagentStop) onto the real VALID_HOOKS
keys and emit { command, matcher?, timeout? } entries there, merged into the
shared ~/.hermes/config.yaml the same way HermesagentMcp/HermesagentPermissions
already do. Only type: command hooks are supported (Hermes runs shlex-split
commands with shell=False); prompt/http hooks are dropped (already warned
about centrally by HooksProcessor). matcher is only kept on
pre_tool_call/post_tool_call and dropped with a warning elsewhere. Rewrote
toRulesyncHooks to round-trip from the real event keys instead of the old
hooks.rulesync blob, and rewrote the hermesagent-hooks unit tests plus the
hermesagent hooks e2e generation test to assert the real, functioning keys.

Deferred: HermesagentRuleSettablePaths' nonRoot: undefined (folding
non-root topic rules into a single .hermes.md) is left as-is. Hermes's
progressive per-directory context loading only watches for AGENTS.md /
CLAUDE.md / .cursorrules files in subdirectories the agent navigates into,
not nested .hermes.md files, and rulesync's nonRoot convention models a single
flat rules subdirectory rather than mirroring the target project's real
directory tree. Wiring nonRoot here would either do nothing (files Hermes
never scans) or require new per-directory-aware rule architecture, so this is
deferred pending a decision on how to model it rather than shipped as a
guess.

Fixes #2036.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dyoshikawa
dyoshikawa merged commit 9ee90fd into main Jul 1, 2026
9 checks passed
@dyoshikawa
dyoshikawa deleted the resolve-issue-2036-hermesagent-hooks-fix branch July 1, 2026 02:48
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.

Follow up Hermes Agent upstream updates: skills, hooks, and permissions (config.yaml)

2 participants