Documentation
¶
Overview ¶
Package hooks provides an in-process programmatic hook system for the claw-code-go runtime. It complements the public top-level `hooks` package (which executes external shell commands as hooks) by letting consumers register Go callbacks that observe and optionally intercept lifecycle events: pre/post tool execution, user prompt submission, compaction, and session stop.
Design goals:
Zero-cost when no Runner is configured. Call sites use runner.Fire(...) only after a nil-check, and the integration in runtime/conversation.go and runtime/compact.go is a single nil guard plus a Fire call.
Sequential, deterministic dispatch. Handlers for an event run in the order they were registered. The first handler returning a non-Continue decision wins and short-circuits subsequent handlers. This mirrors the Rust plugin hooks runner's "first decision wins" semantics.
Robust to misbehaving handlers. A handler returning an error is logged to stderr and treated as Continue: hook authors should not be able to break a conversation by panicking or erroring inside a hook. Panics in a handler are recovered and treated as a logged error.
Thread-safe registration and dispatch. Handlers can be registered from any goroutine. Fire takes a read lock so concurrent dispatch is fine.
This package is deliberately minimal: it does NOT include a shell-command hook runner (that lives in the public `hooks` package), a plugin lifecycle manager, or on-disk hook configuration. Those layers, if needed, can be built on top of this Runner by registering Handler functions that delegate to whatever transport the consumer prefers.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ActionKind ¶
type ActionKind int
ActionKind enumerates what a Decision instructs the runtime to do.
const ( // ActionContinue lets execution proceed unchanged. This is the // zero-value, so a default-constructed Decision is a Continue. ActionContinue ActionKind = iota // ActionModify rewrites the context. Currently honored only for // UserPromptSubmit, where Decision.Replacement.UserPrompt replaces the // pending user prompt. ActionModify // ActionBlock stops the operation. The runtime treats Block as a tool // refusal (PreToolUse) or a skipped compaction (PreCompact). For events // where blocking is meaningless (PostToolUse, Stop, etc.) a Block // decision is logged and treated as Continue. ActionBlock )
type Context ¶
type Context struct {
Event Event
ToolName string
ToolInput map[string]any
ToolResult string
ToolError error
UserPrompt string
MessageCount int
SessionID string
WorkDir string
Plugin *PluginInfo
}
Context is the payload passed to a Handler. Fields are populated only when relevant to the event:
- PreToolUse / PostToolUse / PostToolUseFailure: ToolName, ToolInput. PostToolUse additionally sets ToolResult; PostToolUseFailure sets ToolError.
- UserPromptSubmit: UserPrompt.
- PreCompact / PostCompact: MessageCount.
- Stop: no event-specific fields.
- PrePluginInstall / PostPluginInstall / PrePluginUninstall / PostPluginUninstall: Plugin (PluginInfo). Pre-events leave Plugin.Error nil; Post-events set it on failure.
SessionID and WorkDir are populated on every event when the runtime knows them. The struct is intentionally flat so it is cheap to copy by value; callers should not retain pointers to it past the Handler invocation.
type Decision ¶
type Decision struct {
// Action determines how the runtime interprets this decision.
Action ActionKind
// Replacement carries the new context fields for ActionModify. Only the
// event-relevant fields need to be set (e.g. UserPrompt for
// UserPromptSubmit). Nil for non-Modify actions.
Replacement *Context
// Reason is a human-readable explanation surfaced in logs and (for
// Block) injected into the synthetic refusal tool_result. Optional.
Reason string
}
Decision is the result of a Handler invocation.
type Event ¶
type Event int
Event identifies a lifecycle stage at which hooks can fire.
const ( // PreToolUse fires immediately before a tool is invoked. A Block decision // causes the runtime to skip the tool and inject a synthetic refusal // tool_result back to the model. PreToolUse Event = iota // PostToolUse fires after a tool returns successfully (no error). PostToolUse // PostToolUseFailure fires after a tool returns with an error. The error // is exposed via Context.ToolError. PostToolUseFailure // UserPromptSubmit fires immediately before the next user prompt is // submitted to the model. A Modify decision replaces Context.UserPrompt // with Decision.Replacement.UserPrompt. UserPromptSubmit // PreCompact fires before history compaction is triggered. A Block // decision instructs the caller to skip compaction this turn. PreCompact // PostCompact fires after compaction completes (whether or not it // actually trimmed messages). PostCompact // Stop fires once at session end (e.g. when the conversation loop tears // down). It is purely observational; decisions are ignored. Stop // PrePluginInstall fires before a plugin is installed. A Block decision // aborts the install: no files are copied, the registry is not mutated, // and Install returns an error so callers can surface the refusal. PrePluginInstall // PostPluginInstall fires after Install completes — successfully or not. // On success, Context.Plugin.Error is nil. On failure, it is set to the // error returned by the install pipeline so observers can audit the // outcome. PostPluginInstall // PrePluginUninstall fires before a plugin is uninstalled. A Block // decision aborts the uninstall: the plugin remains in the registry and // on disk. PrePluginUninstall // PostPluginUninstall fires after Uninstall completes — successfully or // not. Context.Plugin.Error reflects the outcome. PostPluginUninstall )
type Handler ¶
Handler is a hook callback. It receives the Go context and the hook payload and returns a Decision plus an optional error. Errors are logged and treated as Continue.
type Option ¶
type Option func(*Runner)
Option configures a Runner at construction time.
func WithLogger ¶
WithLogger sets the writer used to log handler errors and warnings. Default: os.Stderr. Pass io.Discard to silence the runner.
type PluginInfo ¶
type PluginInfo struct {
ID string
Name string
Version string
Description string
InstallPath string
Source string
Error error
}
PluginInfo carries plugin lifecycle metadata into the hook Context. Defined in this package (rather than in `plugin/`) to keep the hooks runner free of plugin-package imports and avoid an import cycle.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner orchestrates registration and dispatch of programmatic hooks. The zero value is NOT ready to use; construct one via NewRunner.
func (*Runner) Fire ¶
Fire dispatches the given context to all handlers registered for hctx.Event, in registration order. The first handler returning a non-Continue decision wins and is returned immediately.
Errors from a handler are logged and treated as Continue (the chain is not broken). Panics in a handler are recovered, logged, and treated as Continue.
If no handler returns a non-Continue decision (or no handlers are registered), Fire returns ActionContinue, nil.
A nil Runner returns ActionContinue, nil — this is the documented no-op path for callers that haven't been configured with hooks.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package hookstesting provides test helpers for the hooks package.
|
Package hookstesting provides test helpers for the hooks package. |