fix(hermesagent): emit hooks under Hermes's native VALID_HOOKS event keys - #2100
Merged
Merged
Conversation
…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>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bug fix for #2036.
hermesagent-hooks.tswas previously shipped writing the canonical hooks JSON verbatim under an INVALID key (hooks: { rulesync: <canonical JSON> } }). Hermes Agent only recognizes hooks registered under its fixedVALID_HOOKSevent keys, sorulesyncis 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_requestpair). Confirmed the realVALID_HOOKSset 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)
matcheremitted only onpre_tool_call/post_tool_call(dropped elsewhere with a warning).type: "command"hooks emitted — Hermes runs shlex-split commands with no shell, soprompt/httptypes are unsupported and dropped with a warning.toRulesyncHooks) rewritten to round-trip from the real native keys instead of the oldhooks.rulesyncblob.~/.hermes/config.yaml, same pattern asHermesagentMcp/HermesagentPermissions.hooks-processor.tsfactory entry corrected: was declaringCLAUDE_HOOK_EVENTS+["command","prompt","http"](copy-paste artifact), now declares the realHERMESAGENT_HOOK_EVENTS+["command"].Deferred (secondary, out of scope)
The issue also flagged a secondary rules/
nonRootgap (nested per-directory context files). Not implemented: Hermes's progressive per-directory loading only watches forAGENTS.md/CLAUDE.md/.cursorrulesin subdirectories, not nested.hermes.md; wiringnonRootas-is would repeat this exact bug pattern (write files Hermes never reads). Left for a follow-up design.Verification
pnpm cicheckfully 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