Skip to main content

Custom Hooks

Hook support varies by platform and by event — see the per-event matrix below.
  • SessionStart hook / extension ships on Claude Code, Cursor, OpenCode, Gemini CLI, Qoder, CodeBuddy, Copilot, Droid, Pi Agent. Codex relies on AGENTS.md plus the UserPromptSubmit hook; Kiro’s Agent Hooks are user-configured — Trellis does not install any out of the box.
  • PreToolUse / extension sub-agent context injection ships on Claude Code, Cursor, OpenCode, CodeBuddy, Droid, Pi Agent. The other hook-capable platforms rely on a pull-based prelude inside each sub-agent instead.
  • UserPromptSubmit / workflow-state nudge ships on the same platforms as SessionStart, plus Codex when hooks are enabled.
  • Kilo, Antigravity, Devin have no hook primitive at all; behavior is delivered via workflow files + skills.

Hook types

Claude Code, Cursor, CodeBuddy, and Droid share a Python-hook layout compatible with CC’s event model (settings.json or hooks.json referencing Python scripts). OpenCode uses JS plugins (factory functions in .opencode/plugins/) with the same event semantics. Pi Agent uses .pi/extensions/trellis/index.ts instead of Python hook files. Gemini, Qoder, and Copilot use hook/prompt files without PreToolUse. Codex installs the shared UserPromptSubmit workflow-state hook; its retained session-start.py is compatibility code, not the default model-visible startup path.

settings.json configuration (Claude Code)

Configure hooks in .claude/settings.json:
Notes:
  • Each event type is an array of { matcher, hooks } blocks.
  • matcher: pattern to match ("startup" matches session start, "Task" matches Task tool calls, "*" matches everything).
  • hooks: array of commands that run when matched, in order.
  • $CLAUDE_PROJECT_DIR: expanded by Claude Code to the project root.
  • timeout: seconds; if exceeded, the hook is skipped.
Trellis does not install a Claude Code statusLine by default. New installs do not create .claude/hooks/statusline.py or add statusLine to .claude/settings.json, and trellis update never adds one to an opted-out project. Existing projects that already have a statusLine keep it during update. To opt in, run trellis init --with-statusline (interactive non--y init also asks once, default no). This installs .claude/hooks/statusline.py and adds the statusLine command to .claude/settings.json. The status line shows the active task, plus rate-limit reset countdowns and width-adaptive layout. It is Claude-Code-only and stays off unless you pass the flag, so it can never silently override a global statusLine config.

Shipped hooks

session-start.py: context loading

Trigger: SessionStart. What it does:
  • Reads .trellis/.developer for developer identity.
  • Reads .trellis/workflow.md for the workflow contract.
  • Reads .trellis/workspace/{name}/index.md for session history.
  • Reads git log for recent commits.
  • Reads active tasks.
Output: emits all the context as a system message at the start of the session.

inject-workflow-state.py: workflow-state nudge

Trigger: UserPromptSubmit. What it does: parses [workflow-state:STATUS] blocks from .trellis/workflow.md and emits the body matching the active task’s status as a <workflow-state> preamble for the turn. Parser-only — the hook does not embed any fallback body text. When the active task’s status has no matching block, the hook emits the generic line Refer to workflow.md for current step. so the AI re-reads the workflow contract. To customize per-turn wording, edit the [workflow-state:STATUS] block in .trellis/workflow.md. No script change required.

inject-subagent-context.py: spec injection engine

Trigger: PreToolUse, matching Task tool calls. What it does:
  • Intercepts Task tool calls.
  • Reads the JSONL matching the subagent_type (implement.jsonl or check.jsonl).
  • Reads all files referenced in the JSONL.
  • Reads prd.md, design.md if present, and implement.md if present.
  • Assembles the sub-agent prompt (specs + task artifacts + original instructions).
Design decisions:
  • Each sub-agent receives its full context at launch; there is no resume.
  • Only trellis-* sub-agents are hooked; custom sub-agents must opt in by editing this file or using their own injection.

Pi extension: equivalent hook behavior

Pi Agent does not load .py hook scripts. Trellis writes .pi/extensions/trellis/index.ts, which implements the same three behaviors in extension form:
  • session start context injection
  • workflow-state breadcrumb injection
  • sub-agent JSONL and task artifact context injection
It also passes TRELLIS_CONTEXT_ID into Bash commands so task.py start/current/finish can resolve the correct .trellis/.runtime/sessions/<session-key>.json file for the current Pi session.

Writing a custom hook

Hooks receive JSON input on stdin and emit JSON results on stdout. Input format (PreToolUse example):
Output format:

Example: an auto-test hook

.claude/hooks/auto-test.py:
Register it in settings.json: