Skip to content

feat: make global checklists work with Hermes Agent (and every polyhook caller) - #213

Merged
tupe12334 merged 2 commits into
mainfrom
feat/hermes-agent
Sep 24, 2026
Merged

tupe12334 merged 2 commits into
mainfrom
feat/hermes-agent

Conversation

@tupe12334

Copy link
Copy Markdown
Member

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.

Nothing else needed changing for Hermes. polyhook 0.1.11 already maps pre_tool_call to tool:before, terminal to bash, tool_input, session_id, and on_session_end to session:stop. Existing checklists (on_tool = "bash", input.command...) match Hermes shell commands unchanged.

Test plan

  • cargo test passes: 115 lib, 35 bin, 18 cli, 11 integration.
  • New tests: Claude Code deny shape; Hermes block shape with ack.sh; Hermes approve is {}; Hermes session_id scopes state; Hermes on_session_end cleans the global session; binary run with a sandboxed HOME plus HERMES_REAL_HOME still finds the shared global checklist; HERMES_REAL_HOME resolution order.
  • With the old 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, and cargo doc with -D warnings all pass.
  • Live through real Hermes v0.21.4 (hermes hooks test, isolated HERMES_HOME, empty project, global dir from steplock init --global):
    • git push: parsed (Hermes wire shape): {"action": "block", "message": "[example-gate: 1/2] ..."}.
    • After ack.sh: step 2/2 blocks. After the second ack.sh: approved ({}).
    • on_session_end: steplock: cleaned up session ...; the global sessions/ directory is empty.
    • Nothing was written to the project directory.
  • Not reproduced live: the sandboxed HOME case. On this macOS host Hermes keeps HOME as the real home even with TERMINAL_HOME_MODE=profile (both HOME and HERMES_REAL_HOME point 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

…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
tupe12334 marked this pull request as ready for review September 24, 2026 10:05
@tupe12334
tupe12334 merged commit 25bf498 into main Sep 24, 2026
13 checks passed
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.

Hook responses are always serialized in legacy Claude Code format — caller context discarded by parsing stdin instead of reading it

1 participant