hooks

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package hooks owns mivia's deterministic lifecycle-hook layer: the config shape and the isolated execution path hook commands run through.

The package deliberately imports neither internal/runtime nor internal/tools. Hooks are out-of-band process execution: they never construct a runtime.Request and never call Dispatcher.Invoke, so a PreToolUse hook that matches a tool cannot dispatch that tool and recurse. The boundary is pinned by a test rather than stated in a comment, because a comment does not survive a refactor.

Index

Constants

View Source
const (
	MinTimeout = time.Second
	MaxTimeout = 600 * time.Second
)

Timeout bounds. A handler may not opt out of having one.

View Source
const HandlerTypeCommand = "command"

HandlerTypeCommand is the only handler type v1 implements.

View Source
const MaxOutputBytes = 8 << 10

MaxOutputBytes bounds the total hook-supplied context one invocation may produce, across every handler that ran. Over-budget output is TRUNCATED with a notice rather than refused: unlike tool output, which the dispatcher destroys because an undeclared result cannot be bounded, hook stdout is advisory, and destroying a tool's result because its formatter was chatty is the worse failure.

Variables

This section is empty.

Functions

This section is empty.

Types

type Event

type Event string

Event is a lifecycle point a hook may be attached to.

const (
	// EventPreToolUse fires after the dispatcher reserved an invocation and
	// before the handler executes. It is the only event that can block.
	EventPreToolUse Event = "PreToolUse"
	// EventPostToolUse fires after the handler returned. Reactive only.
	EventPostToolUse Event = "PostToolUse"
	// EventStop fires when the root loop's turn ends. Observation only.
	EventStop Event = "Stop"
)

func V1Events

func V1Events() []Event

V1Events are the lifecycle events this version implements.

type Group

type Group struct {
	Event    Event
	Matcher  string
	Handlers []Handler
	// Source is the config file that declared this group.
	Source string
	// Project marks a group declared by the WORKSPACE's config rather than the
	// user's. It changes nothing about execution and everything about display:
	// "this hook came with the repository" is the one fact a reader needs, and
	// deriving it from the Source path at each surface would put the answer in
	// several places at once.
	Project bool
	// Index is the group's position in the [[hooks]] array, for error messages.
	Index int
	// contains filtered or unexported fields
}

Group is one [hooks] table: an event, a tool-name matcher, and handlers.

func Parse

func Parse(data []byte, sourcePath string) ([]Group, error)

Parse validates the [hooks] tables in a mivia config file.

Rejection is the deliverable: no value is ever coerced onto the permissive branch, because coercion toward permissive is precisely how a hook config fails open. An unrecognised on_timeout is an error, not "allow"; an unrecognised event is an error, not a skip.

func (Group) Matches

func (g Group) Matches(toolName string) bool

Matches reports whether a tool name is covered by this group's matcher.

The pattern is compiled at load, not here: a matcher that failed to compile on the hot path of a security gate would have no honest verdict to return - allowing would open the gate, denying would break every tool call.

Matching is deliberately unanchored, as it is in every harness whose configs users will copy from. `matcher = "run"` therefore covers `run_command`, and over-matching a gate errs toward more hooks firing, not fewer.

type Handler

type Handler struct {
	// Type is always HandlerTypeCommand in v1.
	Type string
	// Argv is an explicit argument vector, never a shell string. argv[0] is a
	// filesystem path resolved against the declaring config file's directory.
	Argv []string
	// Timeout is the wall-clock bound for one execution.
	Timeout time.Duration
	// OnTimeout is the verdict when Timeout expires.
	OnTimeout TimeoutVerdict
}

Handler is one command a matching event runs.

type Outcome

type Outcome struct {
	// Denied is true only for PreToolUse. Reactive events cannot block.
	Denied bool
	// Reason is the block reason. It reaches the model - that is the entire
	// point of a block.
	Reason string
	// Context is hook-supplied advisory text, bounded by MaxOutputBytes.
	Context string
	// Warnings are operator-facing diagnostics; they never reach the model.
	Warnings []string
	// Runs records every handler that actually executed, in order.
	//
	// It exists so the operator can see a hook fire. Without it, a hook that
	// runs on every write is invisible until it says something, and "did my
	// formatter run?" has no answer short of instrumenting the script - which
	// is how a silently mis-matched matcher survives for weeks.
	Runs []Run
}

Outcome aggregates every handler that ran for one event.

type Payload

type Payload struct {
	Event      Event           `json:"event"`
	Tool       string          `json:"tool,omitempty"`
	Input      json.RawMessage `json:"input,omitempty"`
	SessionID  string          `json:"session_id,omitempty"`
	TurnID     string          `json:"turn_id,omitempty"`
	ToolCallID string          `json:"tool_call_id,omitempty"`

	// File is exported to the hook as MIVIA_FILE. It is never spliced into an
	// argv, so a filename containing shell syntax is inert.
	File string `json:"-"`
}

Payload is the JSON object written to a hook's stdin. The field names mirror Claude Code and Codex so hook scripts port between harnesses.

type Run

type Run struct {
	Event   Event
	Tool    string
	Program string
	Denied  bool
	// Output is what this handler produced: its advisory text, or the reason it
	// blocked. Empty means it ran and said nothing, which is the normal case
	// for a formatter and is still worth showing.
	Output string
	// Warning is the operator diagnostic this handler produced, if it
	// misbehaved - a timeout, a crash, an exit code with no decision in it.
	Warning string
}

Run is one handler execution, recorded for display.

It is deliberately separate from Context: Context is what the MODEL is told, bounded and merged across handlers, while a Run is what the OPERATOR is shown, attributed to the script that produced it. Merging the two would mean either showing the operator less than happened or telling the model more than it needs.

type Runner

type Runner struct {
	// WorkspaceRoot is the working directory hook commands run in.
	WorkspaceRoot string
}

Runner executes hook commands out-of-band.

Hooks deliberately do NOT reuse run_command's execution path. That path refuses a path-shaped argv[0], requires the program to be on the run allowlist, pins cwd to the workspace root, refuses secret-like paths, and wraps output in a command:/cwd:/exit= header that would corrupt the JSON protocol. Adding hook scripts to the run allowlist to make them fit would be worse: it would hand the MODEL the ability to invoke them.

func (Runner) Run

func (r Runner) Run(ctx context.Context, groups []Group, payload Payload) Outcome

Run executes every handler in groups whose event and matcher select payload.

PreToolUse stops at the first deny: the call is not happening, and the remaining handlers have side effects. Reactive events run every handler, because none of them can veto and each exists for its own effect.

type TimeoutVerdict

type TimeoutVerdict string

TimeoutVerdict is what a handler's timeout means for the event's decision.

const (
	// OnTimeoutBlock denies the call. It is PreToolUse's default: a hung gate
	// must not be an open gate, or an attacker who can make a hook hang - and
	// so can an ordinary flaky script - has disabled the control.
	OnTimeoutBlock TimeoutVerdict = "block"
	// OnTimeoutAllow warns and continues. Reactive events default to it: a slow
	// formatter must not stop work.
	OnTimeoutAllow TimeoutVerdict = "allow"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL