Declarative hook framework for Claude Code
Project description
captain-hook
Guardrails for Claude Code, written as typed, testable data — and learned from the corrections you give Claude.
A captain-hook hook is declarative Python: an event, some conditions, an action. Block a footgun before it runs, nudge the agent off a bad pattern, gate "done" until the tests pass. Then captain-hook closes the loop: it reads the corrections you give Claude as you work and opens pull requests that codify the durable ones as new hooks. You write the first few; it writes the rest.
Install
captain-hook needs no install — it runs through uvx. From your project root:
uvx capt-hook init
init scaffolds .claude/hooks/, wires Claude Code's settings, registers the captain-hook plugin so its skills install on workspace-trust, and arms the session reviewer. Or do it all from a session. Run /plugin marketplace add yasyf/captain-hook, then ask Claude to "set up captain hook".
Your first hook
A hook is an event, some conditions, and an action. This one stops the agent from finishing a UI change it never looked at:
# .claude/hooks/visual_review.py
from captain_hook import gate, TouchedFile, UsedSkill
gate(
"You edited UI files. Open them with agent-browser and verify they render before finishing.",
only_if=[TouchedFile("**/src/routes/**", "**/src/components/**")],
skip_if=[UsedSkill("agent-browser")],
)
only_if arms the gate only when UI files changed; skip_if stands it down once the agent has done the review. Conditions match tools, files, commands, and even which skills the agent used.
It learns from your corrections
Most hooks you'll never write by hand.
The corrections you give Claude as you work are exactly the rules a hook should enforce: "never force-push", "use uv, not pip", "you weakened that test". Writing the hook by hand is friction you skip in the moment, so the session reviewer notices for you. When a session ends, it reads the transcript, finds the durable corrections and the hooks that misfired, judges which ones are standing rules and which are one-offs, and once a pattern proves itself across sessions, opens a pull request that adds the hook — or fixes the one that misfired. You review the PR like any other.
It's on by default after init. Turn it off for a repo with uvx capt-hook review disable. The session reviewer guide covers the prerequisites (an authenticated claude and gh) and the HOOKS_REVIEW_* thresholds.
Tested like code
Every deterministic hook carries inline tests, so a broken hook fails like broken code:
# .claude/hooks/safety.py
from captain_hook import Allow, Block, Input, block_command
block_command(
["git", "stash"],
reason="Use the team's VCS workflow for shelving changes",
hint="Commit a WIP change instead of stashing",
tests={
Input(command="git stash"): Block(),
Input(command="git status"): Allow(),
},
)
Run them from your project root, where --hooks defaults to .claude/hooks:
uvx capt-hook test
Wire that into CI and you catch a broken hook the way you catch broken code.
What it's for
- Block footguns before they run on
PreToolUse: force-push,rm -rf, package-manager traps. - Steer the agent with feedback that fires on the patterns it actually emits: repeated failures, weakened tests, missed conventions.
- Hold the line on multi-step work with Stop gates and artifact checks, so the agent can't call it "done" before the tests run or the report's written.
- Keep all of it testable; every hook ships with inline tests that run in CI.
Docs
Read the docs for the full guide to conditions, primitives, LLM hooks, workflows, state, and real-world patterns. To work on captain-hook itself, see the development guide.
License
Licensed under PolyForm Noncommercial 1.0.0, free for noncommercial use.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file capt_hook-4.3.0.tar.gz.
File metadata
- Download URL: capt_hook-4.3.0.tar.gz
- Upload date:
- Size: 160.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a15f3dcd8383660ed9f84eda18946e9b9419fa7f7e1fbbe544ce11ba2de932d
|
|
| MD5 |
cc29a31f3a3b2de98f569f678ea281ab
|
|
| BLAKE2b-256 |
c61cb06ae9ce1a04608278610c129182a41ff65601f8a777dd2ed20c4e326f01
|
Provenance
The following attestation bundles were made for capt_hook-4.3.0.tar.gz:
Publisher:
release-pypi.yml on yasyf/captain-hook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
capt_hook-4.3.0.tar.gz -
Subject digest:
5a15f3dcd8383660ed9f84eda18946e9b9419fa7f7e1fbbe544ce11ba2de932d - Sigstore transparency entry: 2042646981
- Sigstore integration time:
-
Permalink:
yasyf/captain-hook@43151a7a0e58750b1475025796feee13cec8def2 -
Branch / Tag:
refs/tags/v4.3.0 - Owner: https://github.com/yasyf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@43151a7a0e58750b1475025796feee13cec8def2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file capt_hook-4.3.0-py3-none-any.whl.
File metadata
- Download URL: capt_hook-4.3.0-py3-none-any.whl
- Upload date:
- Size: 209.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0dc41a976eba449b8e91782178b2a50f891860eee06e1b809e9e20237e8de6f2
|
|
| MD5 |
e495db82fad066a1671040e90c302e40
|
|
| BLAKE2b-256 |
c92eae9ae939ca172983a9820fbe52bbd3797a19bcbe7639e0ebcea86887d51a
|
Provenance
The following attestation bundles were made for capt_hook-4.3.0-py3-none-any.whl:
Publisher:
release-pypi.yml on yasyf/captain-hook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
capt_hook-4.3.0-py3-none-any.whl -
Subject digest:
0dc41a976eba449b8e91782178b2a50f891860eee06e1b809e9e20237e8de6f2 - Sigstore transparency entry: 2042647299
- Sigstore integration time:
-
Permalink:
yasyf/captain-hook@43151a7a0e58750b1475025796feee13cec8def2 -
Branch / Tag:
refs/tags/v4.3.0 - Owner: https://github.com/yasyf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@43151a7a0e58750b1475025796feee13cec8def2 -
Trigger Event:
push
-
Statement type: