Skip to content

Automating with hooks

An event hook runs an agent turn when a lifecycle event fires. This guide is the practical companion to that concept page: it walks through creating an onArchive hook — “when I archive a chat, tidy up after it” — granting it exactly the tools it needs, enabling it, and reading the run it produces. By the end you’ll also know how to let Claude manage a project’s own hooks, and how to steer the built-in curator.

Event hooks are managed as triggers, so everything below happens on a project’s Triggers tab. (Triggers also cover time-driven schedules; this guide is about the event type.)

Open any project and click its Triggers tab (/projects/<slug>/triggers). Each project has its own trigger list — a hook is per-project, versioned in that project’s project.yaml. The list starts empty; each row will show a trigger’s type, its firing condition, its capability, and whether it’s enabled.

Click Add trigger to open the editor, then:

  1. Name it — e.g. tidy-up. Letters, numbers, ., _, - (renaming means deleting and recreating, so the name is stable).
  2. Set Type to Event — a lifecycle event fires it.
  3. Set Event to On archive — a chat is archived (onArchive). (The dropdown also offers After turn, but that one customizes the post-turn sweeper rather than a general hook — leave it on On archive here.)
  4. Write a Prompt — what the hook should do when it fires. Choose Inline text for a short instruction, or Prompt file to keep it in a git-tracked .md file under .paddock/triggers/ that Paddock reads fresh on each fire (handy for a longer runbook Claude can edit over time).

The Triggers tab editor for an event hook: Type set to Event, Event set to On archive, and an inline prompt

A hook’s granted tools are its whole capability, so the editor’s Tools section is the most important part. Check exactly the tools the hook needs — nothing else is available to it:

The Triggers tab capability picker: grouped tool checkboxes (Read, Write &#x26; execute, Web, Orchestration, Browser) with Write checked

  • Leave everything unchecked for a tool-less hook that only reads its prompt and returns text.
  • Check Write to let it author a file (a log, an OVERVIEW.md).
  • Check Bash only when the hook genuinely needs a shell — it lets the hook run arbitrary commands in the project working directory, so the editor flags it with a warning. That’s the grant a real “spin down the dev server and delete the clone” cleanup hook needs.

The other capability knobs sit below the picker: a permission mode, an optional model override, a max turns bound (default 30), a max spawn depth, and whether the hook accretes into one long-lived chat or starts a fresh chat each fire.

New triggers are created disabled so nothing fires the instant you save. Tick Enabled (fires on its trigger) when you’re ready — or save it disabled and flip it on later with the row’s Enable button. Enabling and disabling is just that toggle; there’s no separate “arm” step, and disabling a hook keeps its definition (and its past runs) intact.

Click Create trigger and the hook is live. It now shows in the list as an Event trigger firing on onArchive, with a capability summary and an Enabled status.

Trigger it: archive a chat in this project (the archive action in the chat list, or Claude’s own archive_chat). The archive completes immediately, and just after it commits your hook fires as its own agent turn. A new chat appears in the sidebar with a ⚡ lightning badge, and opening it floats a read-only capability banner at the top — what fired it, and the exact tools it was granted:

A hook chat: the ⚡ badge in the sidebar, and the capability banner naming the onArchive event, the granted tools, permission mode, max turns, and agent

The banner is projected from the same agent config the runtime enforces, so it’s truthful by construction — it can’t show a capability the hook doesn’t have. The hook never blocks or fails the archive: even if the hook errors, the chat you archived is still archived.

Manage hooks from an agent (the manager pattern)

Section titled “Manage hooks from an agent (the manager pattern)”

You don’t have to use the tab. Claude can declare, edit, and delete a project’s own hooks through the hook-management MCP tools — the agent twin of the Triggers tab:

  • list_triggers — enumerate the project’s triggers (hooks and schedules).
  • set_trigger — create or update one. It’s create-or-update by name, so enabling/disabling is just set_trigger with enabled flipped; a brand-new hook defaults to disabled. For an event hook you pass type: "event" and event: "onArchive", a prompt (or prompt_file), and the capability (tools, permission_mode, model, max_turns).
  • remove_trigger — delete one.

This is what makes “set yourself up to tidy the project whenever I archive a chat” a thing you can ask Claude to do directly.

One related knob isn’t a hook you create but a file you drop in. The sweeper — the tool-less agent that curates OVERVIEW.md and CHANGELOG.md after your turns — reads an optional, git-tracked .paddock/hooks/sweep.md in the project. When present and non-blank, its contents are appended to the sweeper’s prompt as extra project-specific curator instructions (e.g. “always keep a Glossary section”, “note API changes prominently”). When it’s absent, curation is exactly unchanged, and a missing or unreadable file never breaks a sweep. It only shapes how the curator writes; it grants no new capability.

  • Event hooks — the concept: why the tools are the capability, and the fire-and-forget, after-commit guarantee.
  • The sweeper — the curator you steer with .paddock/hooks/sweep.md.
  • Hooks reference — the project.yaml schema and the exact hook-management MCP arguments.
  • Environment variables — the PADDOCK_HOOKS_MCP opt-in and other box defaults.