Skip to content
Peter Pak edited this page Apr 15, 2026 · 3 revisions

Hooks

Hooks are shell commands that Gemini CLI runs automatically in response to session lifecycle events. For rocketsmith, a SessionStart hook injects the dependency status into context so the agent knows whether Java, OpenRocket, and PrusaSlicer are installed before saying a word.

Claude Code: Claude Code does not use Gemini-style hooks. Dependency status is surfaced via rocketsmith_setup, which the agent calls at session start. The hooks described on this page are Gemini CLI only.

Structure

hooks/
├── hooks.json       ← declares the hook event and calls run-hook.cmd
├── run-hook.cmd     ← cross-platform polyglot wrapper (Windows batch + Unix bash)
└── session-start    ← actual hook logic, named after the event, no file extension

This structure follows the convention from obra/superpowers.

hooks.json

Declares which lifecycle events trigger which commands. Must live at hooks/hooks.json inside the extension directory — not inside gemini-extension.json (hooks is not a valid field there).

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|clear|compact",
        "hooks": [
          {
            "type": "command",
            "command": "\"${extensionPath}/hooks/run-hook.cmd\" session-start",
            "async": false
          }
        ]
      }
    ]
  }
}

${extensionPath} is substituted by Gemini CLI with the path to the installed extension directory.

run-hook.cmd

A polyglot script that runs as both a Windows batch file and a Unix shell script. Its job is to find bash on whatever platform the user is on and delegate to the named hook script.

  • On Unix: the batch block is skipped (: << 'CMDBLOCK' hides it from bash); the Unix section at the bottom runs directly
  • On Windows: cmd.exe executes the batch block, which searches for bash in Git for Windows locations and on PATH
  • If no bash is found on Windows, it exits silently — the session still works, just without the hook context

The script takes the hook name as its first argument (session-start) and passes any remaining args through.

session-start

The actual hook logic. Named after the event it handles, with no file extension (following the superpowers convention — file extensions on hook scripts interfere with some platform auto-detection).

What it does:

  1. Determines the extension root using SCRIPT_DIR (dirname "$0") — more reliable than env var injection
  2. Checks for Java (java on PATH), the OpenRocket JAR (standard platform paths + $OPENROCKET_JAR), and PrusaSlicer (prusa-slicer/PrusaSlicer on PATH) directly via shell commands
  3. Builds a status summary and lists any missing dependencies
  4. JSON-escapes the output (backslashes, quotes, newlines)
  5. Outputs {"additionalContext": "..."} — the format Gemini CLI expects from hooks

Hook Output Format

Gemini CLI hooks must output valid JSON to stdout. Plain text output is ignored. The SDK-standard format is:

{"additionalContext": "text to inject into agent context"}

Debugging output should go to stderr, not stdout, to avoid corrupting the JSON.

The injected text appears in the agent's context as:

# rocketsmith dependency status
java: installed (/path/to/jvm)
openrocket: installed (/path/to/openrocket.jar)
prusaslicer: installed (/path/to/prusa-slicer)
status: ready — all tools available

If any dependency is missing, status reads NOT READY — missing: <dep>. Call rocketsmith_setup(action='install') to install.

Why a Hook Instead of a Tool Call

The previous approach required the agent to call rocketsmith_setup(action="check") as its first turn in every session. This had two problems:

  1. It cost a turn and added latency before the agent could do any useful work
  2. The agent could forget to do it

The SessionStart hook runs automatically before the agent's first turn, so the dependency status is already in context when the agent starts. rocketsmith_setup(action="install") is still a tool for when installation is needed — that requires user confirmation and can take a while.

Adding New Hooks

To add a hook for a different lifecycle event (e.g. BeforeTool):

  1. Add an entry to hooks/hooks.json under the new event key
  2. Create a corresponding script in hooks/ named after the event
  3. Make the script executable: chmod +x hooks/<name>
  4. Ensure the script outputs {"additionalContext": "..."} JSON to stdout

Available Gemini CLI hook events: SessionStart, SessionEnd, BeforeAgent, AfterAgent, BeforeModel, AfterModel, BeforeToolSelection, BeforeTool, AfterTool, PreCompress.

Clone this wiki locally