How hooks.json Influences OpenAI Plugin Execution Flow: A Deep Dive into the Declarative Hook System
The hooks.json file acts as a declarative configuration that maps lifecycle events like PostToolUse and Stop to custom shell commands, allowing plugins to inject post-processing scripts at specific execution points without modifying the core runtime.
The openai/plugins repository implements a flexible hook system that lets developers extend plugin behavior through declarative configuration rather than code changes. By placing a hooks.json file in a plugin's root directory, authors can intercept lifecycle events and execute custom scripts when specific tools are invoked. This mechanism keeps the core runtime generic while enabling powerful, per-plugin customizations that trigger during the execution flow.
Understanding the hooks.json Structure
Each hooks.json file resides in a plugin's folder (such as plugins/replayio/hooks.json or plugins/figma/hooks.json) and contains a top-level hooks object that defines the extension points.
Event-to-Command Mapping
The keys of the hooks object represent lifecycle events emitted by the runtime, such as PostToolUse and Stop. Each event maps to an array of hook groups, where every group contains a hooks array specifying the actions to execute. Currently, the supported hook type is "command", which instructs the runtime to spawn a shell process.
The Matcher Pattern System
Hook groups may include an optional "matcher" field containing a regular-expression-style pattern. When an event fires, the runtime evaluates this pattern against the tool name that triggered the event. Only groups whose matcher matches the tool name are considered for execution, enabling fine-grained control over when scripts run.
How hooks.json Shapes Plugin Execution Flow
The integration of hooks.json into the plugin execution flow follows a declarative pipeline that extends the built-in behavior without requiring core engine modifications.
Loading and Parsing
When the OpenAI plugin runtime boots a plugin, it automatically reads hooks.json from the plugin directory and parses the configuration. This happens during initialization, making the hook definitions available before any tools are executed.
Event Matching and Filtering
As the execution flow progresses and events like PostToolUse fire, the runtime performs the following steps:
- Retrieves the array of hook groups associated with the event name.
- Evaluates the
matcherpattern against the triggering tool name (e.g., checking if"Bash"matches the regex). - Selects only the hook groups where the matcher succeeds or where no matcher is defined (indicating the hook applies to all tools).
Command Execution
For each matching hook group, the runtime processes the "hooks" array. When encountering a "command" type, it spawns the specified shell command in the plugin's working directory. This allows the plugin to perform side effects such as uploading artifacts, syncing state, or cleaning up resources after specific tool invocations.
Real-World Examples from the Repository
The following examples from the openai/plugins repository demonstrate how hooks.json influences execution flow in production plugins.
Replay.io Plugin Hooks
Located at plugins/replayio/hooks.json, this configuration intercepts two key events:
- PostToolUse with Bash: When the
Bashtool completes, the matcher"Bash"triggers execution of./scripts/post_bash_upload.sh, handling artifact uploads. - Stop event: Without a matcher (applying to all stop events), the runtime executes
./scripts/stop_close_and_upload.shto perform cleanup and final uploads.
Figma Plugin Hooks
The plugins/figma/hooks.json file demonstrates conditional execution based on tool patterns:
- PostToolUse with Write or Edit: The matcher
Write\|Editdetects write or edit operations and runs./scripts/post_write_figma_parity_check.shto validate file consistency.
These examples show how the declarative approach allows different plugins to implement specialized post-processing logic using the same core execution infrastructure.
Runtime Dispatch Implementation
While the core runtime implementation resides elsewhere in the repository, the following TypeScript pseudo-code illustrates how the hook dispatcher processes hooks.json:
// pseudo-code sketch of the hook dispatcher
import * as fs from "fs";
import { spawn } from "child_process";
interface HookSpec {
type: "command";
command: string;
}
interface HookGroup {
matcher?: string; // optional regex pattern
hooks: HookSpec[];
}
type HooksMap = Record<string, HookGroup[]>;
function loadHooks(dir: string): HooksMap {
const path = `${dir}/hooks.json`;
return JSON.parse(fs.readFileSync(path, "utf‑8")).hooks;
}
function runHooks(event: string, toolName: string, dir: string) {
const hooks = loadHooks(dir)[event] ?? [];
for (const group of hooks) {
if (!group.matcher || new RegExp(group.matcher).test(toolName)) {
for (const h of group.hooks) {
if (h.type === "command") {
spawn(h.command, { cwd: dir, shell: true, stdio: "inherit" });
}
}
}
}
}
Calling runHooks("PostToolUse", "Bash", "./plugins/replayio") would invoke ./scripts/post_bash_upload.sh exactly as prescribed by the JSON configuration.
Summary
- Declarative Configuration: The
hooks.jsonfile provides a JSON-based DSL for extending plugin behavior without touching the core runtime code. - Event-Driven Architecture: Hooks respond to lifecycle events like
PostToolUseandStop, inserting custom commands at precise execution points. - Pattern Matching: The optional
matcherfield uses regex patterns to filter which tools trigger specific hook commands, enabling granular control. - Command Execution: Currently supports
"command"type hooks that spawn shell scripts in the plugin's working directory to handle side effects. - Per-Plugin Scope: Each plugin maintains its own
hooks.jsonfile (e.g.,plugins/replayio/hooks.json,plugins/figma/hooks.json), ensuring isolation and easy modification.
Frequently Asked Questions
What lifecycle events can hooks.json respond to?
The hooks.json configuration can map commands to events such as PostToolUse (fired after tool execution) and Stop (fired when the session ends). These events represent specific points in the plugin execution flow where custom scripts can intervene.
How does the matcher pattern in hooks.json work?
The matcher field contains a regular expression pattern that the runtime evaluates against the tool name triggering the event. For example, a pattern of "Bash" matches only the Bash tool, while Write\|Edit matches either Write or Edit operations. If the matcher is omitted, the hook group applies to all tools for that event.
Where should the hooks.json file be located?
The hooks.json file must reside in the plugin's root directory, alongside the plugin's other configuration files. The runtime specifically looks for this file at paths like plugins/{plugin_name}/hooks.json when loading the plugin.
Can hooks.json execute commands other than shell scripts?
Currently, the hooks.json schema supports only the "command" type, which spawns a shell process using the value specified in the "command" field. The command runs in the plugin's working directory, allowing execution of any shell script or system command available in that environment.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →