Reforma
Adding your integration

Hooks

Run commands at agent and plugin lifecycle events.

Hooks let a plugin run commands at specific agent or plugin lifecycle events.

Use hooks for imperative behavior that must happen automatically — for example, preparing an index when a session starts, validating a tool call before it runs, or cleaning up plugin state when the plugin is removed.

If instructions are enough, prefer a skill or rule. Hooks are most useful when the behavior should not depend on the model remembering to do it.

Hooks are Reforma-specific. Agent Plugins does not define a portable hook runtime.

Hooks can come from catalog plugins or from the project's .reforma configuration.

Events

EventWhen
SessionStartBefore the first model turn of a session
SessionEndWhen the agent run ends
UserPromptSubmitBefore the submitted user prompt reaches the model
PreToolUseBefore a tool executes
PostToolUseAfter a tool executes successfully
PostToolUseFailureAfter a tool fails or is denied
StopWhen the agent loop is about to finish
InstallAfter this plugin package is installed or hydrated
UninstallBefore this plugin package is removed

Install and Uninstall run only for the plugin that is being installed or removed.

Stop hooks can prevent the loop from finishing and ask it to continue. Reforma limits hook-driven continuation to three times per run.

Layout

A catalog plugin can put its hook configuration and scripts under hooks/:

hooks/
  hooks.json
  session_start.sh
  pre_bash.sh

When using the conventional hooks/hooks.json path, you do not need to declare a hooks path in the source plugin.json.

Example:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "sh ./hooks/pre_bash.sh",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Invoke bundled shell scripts through sh rather than relying on executable file permissions being preserved.

Matchers

matcher filters tool lifecycle hooks by tool name:

{
  "matcher": "Bash|Shell",
  "hooks": [
    {
      "type": "command",
      "command": "sh ./hooks/check-shell.sh"
    }
  ]
}

Matchers are useful with:

PreToolUse
PostToolUse
PostToolUseFailure

The matcher is interpreted as a regular expression.

For MCP tools, Reforma also resolves the corresponding MCP tool name when matching.

Omit matcher for non-tool lifecycle events.

Command environment

Hook commands run with Reforma directory variables:

VariableValue
REFORMA_PROJECT_DIRProject workspace root
REFORMA_CONFIG_DIRProject .reforma directory
REFORMA_PLUGIN_DIRInstalled plugin package root; plugin hooks only

These variables can also be referenced directly in the command:

{
  "type": "command",
  "command": "sh ${REFORMA_PLUGIN_DIR}/hooks/setup.sh"
}

For plugin hooks, resolved values from extensions.reforma.variables are also added to the subprocess environment using their property names.

This includes configured secret variables. OAuth tokens are not exposed to hook subprocesses; they remain part of MCP authentication.

Project-level hooks do not receive plugin integration variables.

Input

Reforma writes one JSON object to the hook command's stdin.

Every event includes:

{
  "hook_event_name": "PreToolUse",
  "session_id": "...",
  "cwd": "..."
}

Tool events can additionally include fields such as:

{
  "tool_name": "Bash",
  "tool_input": {},
  "tool_use_id": "..."
}

Post-tool events can also include the tool response or failure information.

Output

A hook can write plain text to stdout. Reforma treats that text as additional context for the agent where the event supports it.

For more control, return a JSON object.

Hooks can use structured output to:

  • allow or deny a tool call
  • add agent context
  • replace tool input
  • replace tool output
  • block continuation with a reason

For example, a PreToolUse hook can deny a tool call:

{
  "permissionDecision": "deny",
  "reason": "Do not delete production data."
}

Exit code 2 also blocks the operation. If stderr contains text, Reforma uses it as the reason when no structured reason was returned.

Other hook failures are fail-open: Reforma logs the failure and continues.

The default hook timeout is 30 seconds. Set timeout on an individual command to override it.

Project hooks

Project-level hooks live at:

.reforma/hooks/hooks.json

Their working directory is .reforma.

They receive the project directory variables, but not plugin-specific integration variables or REFORMA_PLUGIN_DIR.

Packing

Catalog plugin authors should use hooks/hooks.json as the canonical source layout.

During packing, Reforma writes the canonical hooks path and generated hook metadata into the packed plugin manifest.

The packer also normalizes supported vendor plugin-root placeholders such as Claude, Cursor, and Codex plugin roots to the Reforma plugin location.

Treat those rewrites as import compatibility; new Reforma hooks should use the canonical layout directly.

On this page