Skip to content
中文

Installing a hook: event translation

Claude Code runs a hook before a tool call on the PreToolUse event. Gemini CLI calls the same moment BeforeTool, and Cursor calls it preToolUse. A hook copied across unchanged would never fire. Tool names differ too: Claude Code’s shell tool is Bash, while Gemini CLI’s is run_shell_command.

  1. Read. HarnessKit reads the hook’s event, matcher and command from the source agent. Nothing else travels, such as a timeout.
  2. Translate the event. HarnessKit looks the event up in a table keyed by Claude Code’s names. If the target has no equivalent, the install fails. The detail panel says only “Failed to install to Hermes”. The reason, such as “Hook event ‘Stop’ is not supported by hermes”, shows only in the summary of a bulk install.
  3. Append. HarnessKit adds the command under the translated event in the target’s hook file. A command that is already there is not added twice.

Some real pairs from that table:

Claude Code eventTarget agentTarget’s name
PreToolUseGemini CLIBeforeTool
PreToolUseCursorpreToolUse
PreToolUseHermespre_tool_call
UserPromptSubmitGemini CLIBeforeAgent
StopDevin Desktoppost_cascade_response
StopHermesnone, so the install fails

Ten agents have hooks in HarnessKit: Claude Code, Codex, Gemini CLI, Cursor, Copilot, Devin Desktop, Hermes, Kiro, Grok Build and Qoder CN. Codex, Copilot, Kiro, Grok Build and Qoder CN use Claude Code’s names for the events they share with it.

Devin Desktop has 12 hook events, and HarnessKit maps only two of them on purpose. Devin Desktop splits tool use into reading code, writing code, running commands and MCP calls. Mapping any one of these to PreToolUse would change how often the hook fires, so HarnessKit leaves them unmapped.

Here is a Claude Code hook that checks shell commands:

~/.claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/bin/check-command.sh" }]
}
]
}
}

After you install it to Gemini CLI, Gemini CLI’s settings file gains this entry:

~/.gemini/settings.json
{
"hooks": {
"BeforeTool": [
{
"hooks": [
{
"command": "~/bin/check-command.sh",
"type": "command"
}
],
"matcher": "Bash"
}
]
}
}

The event became BeforeTool. The matcher is still Bash, and Gemini CLI has no tool by that name. Change it to run_shell_command and the hook fires.

Grok Build keeps a disabled-hooks file, and Kiro has an enabled flag on each hook. HarnessKit uses those. On the other agents, disabling removes the hook from its file. HarnessKit keeps a snapshot, an exact copy, in its database, as for MCP servers.

  • crates/hk-core/src/adapter/hook_events.rs holds the event tables, keyed by Claude Code’s names.
  • crates/hk-core/src/service.rs translates the event during an install, and nothing else.
  • crates/hk-core/src/deployer.rs has one writer per hook format and decides which formats keep the matcher.
  • crates/hk-core/src/manager.rs enables and disables hooks.
  • src/lib/agent-capabilities.ts greys out Install to Agent tiles by hook and scope support, not by event.