feat: make global checklists work with Hermes Agent (and every polyhook caller) - #213
Merged
Merged
Conversation
…ok caller) - Read stdin through `polyhook::read_from` so `polyhook::respond` knows the caller and answers in its own wire format (fixes #196, supersedes #197). Hermes gets `{"action":"block","message":...}`; Claude Code gets a PreToolUse `permissionDecision: "deny"` instead of the legacy top-level `decision: "block"` that ended the whole session. - Prefer `$HERMES_REAL_HOME` over `HOME` when resolving the global directory, so Hermes' sandboxed HOME (containers, TERMINAL_HOME_MODE=profile) sees the same global checklists as every other agent. - Tests: Claude Code and Hermes response shapes, Hermes session scoping and on_session_end cleanup, and a binary test with a sandboxed HOME. - Docs: Hermes Agent setup (pre_tool_call + on_session_end hooks, consent, `hermes hooks test`). - Promote clippy::map_flatten to deny. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Matches the <module>_tests.rs naming used for unit tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tupe12334
marked this pull request as ready for review
September 24, 2026 10:05
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
Makes steplock, including global checklists, work the same under Hermes Agent as under Claude Code, and fixes response formatting for every non-Claude caller.
run_appparsed the stdin bytes directly withparse::parse_event. That skippedpolyhook::read_from, which records the detected caller forpolyhook::respond, so every response fell back to the legacy Claude Code shape. It now reads throughread_from:{"action": "block", "message": ...}.PreToolUsepermissionDecision: "deny". The old top-leveldecision: "block"ended the whole Claude session instead of denying one tool call, which made "retry your original command" impossible.main.rs. This PR is rebased on the currentmain, so fix(cli): preserve caller context so responses use the agent's wire format #197 can be closed.TERMINAL_HOME_MODE=profile, Hermes gives hooks a per-profileHOMEand exports the real one asHERMES_REAL_HOME(hermes_constants.apply_subprocess_home_env).dirs::home_dir()readsHOMEfirst, so steplock would have picked a different "global" directory under Hermes. It now prefers an absolute$HERMES_REAL_HOME.STEPLOCK_GLOBAL_DIRandXDG_CONFIG_HOMEstill win over it.pre_tool_callwithmatcher: "terminal", andon_session_endfor cleanup.hooks_auto_acceptrequirements.hermes hooks testfor verification.workdir. Global checklists don't care; a project.steplock/is found only when Hermes was started in that project.clippy::map_flattenis nowdeny, placed away from fix(ci): make MSRV real — raise to Rust 1.88 and actually test on it #212's line to avoid a merge conflict.Nothing else needed changing for Hermes. polyhook 0.1.11 already maps
pre_tool_calltotool:before,terminaltobash,tool_input,session_id, andon_session_endtosession:stop. Existing checklists (on_tool = "bash",input.command...) match Hermes shell commands unchanged.Test plan
cargo testpasses: 115 lib, 35 bin, 18 cli, 11 integration.ack.sh; Hermes approve is{}; Hermessession_idscopes state; Hermeson_session_endcleans the global session; binary run with a sandboxedHOMEplusHERMES_REAL_HOMEstill finds the shared global checklist;HERMES_REAL_HOMEresolution order.main.rs, the two response-shape tests fail (33 passed, 2 failed), so they catch the bug.cargo clippy --all-targets -- -D warnings,cargo fmt --check, andcargo docwith-D warningsall pass.hermes hooks test, isolatedHERMES_HOME, empty project, global dir fromsteplock init --global):git push:parsed (Hermes wire shape): {"action": "block", "message": "[example-gate: 1/2] ..."}.ack.sh: step 2/2 blocks. After the secondack.sh: approved ({}).on_session_end:steplock: cleaned up session ...; the globalsessions/directory is empty.HOMEcase. On this macOS host Hermes keepsHOMEas the real home even withTERMINAL_HOME_MODE=profile(bothHOMEandHERMES_REAL_HOMEpoint at the user home), so the rewrite only happens in containers or profile-home setups. That path is covered by the unit and binary tests.🤖 Generated with Claude Code