harness-hooks
Installs the hooks and settings that config files list into Claude Code and Codex, and removes them. Works on macOS and Linux.
Run it with uv:
uvx harness-hooks install path/to/config.json
uvx harness-hooks remove path/to/config.json
Or install it once, which puts harness-hooks on your PATH:
uv tool install harness-hooks
Config
A config is a JSON file with a name and any of these parts:
hooks: commands the agents run at their events.envandsettings: values for the agents' settings files.inputs: questions thatinstallasks, such as for a token.files: files the hooks need.agents, such as["claude"]: limits the config to those agents.
Hooks
{
"name": "notify",
"files": ["notify.py"],
"hooks": {
"Stop": [{"command": "python3 {dir}/notify.py {agent}", "timeout": 5}],
"claude": {
"Notification": [{"matcher": "idle_prompt", "command": "python3 {dir}/notify.py claude"}]
},
"codex": {
"PreToolUse": [{"matcher": "^request_user_input", "command": "python3 {dir}/notify.py codex"}]
}
}
}
- Events in
hooksgo to both agents; events inhooks.claudeandhooks.codexonly to that agent. {dir}becomes the folder with the hooks' files, so hooks can call scripts there.{agent}becomesclaudeorcodex.matcherandcommandmean what they mean in each agent's own hook files. Other fields, such astimeout, are copied as they are.files, if given, lists the files the hooks need, as paths inside the config's folder.
Settings
A config can set agent settings and ask for personal values, such as keys:
{
"name": "tracing",
"inputs": {
"key": {"ask": "Tracing key", "secret": true},
"token": {"ask": "Gateway token", "secret": true, "file": true}
},
"env": {"TRACE_HOST": "https://trace.example.com", "TRACE_KEY": "{key}"},
"settings": {
"claude": {"apiKeyHelper": "cat {token}"},
"codex": {"model_providers": {"gateway": {"auth": {"command": "cat", "args": ["{token}"]}}}}
}
}
envsets environment variables for each selected agent.settings.claudeholds keys of Claude'ssettings.json,settings.codexkeys of Codex'sconfig.toml.inputsare questions thatinstallasks;{key}inenvandsettingsbecomes the answer. Secret answers don't show as you type.- With
"file": true, the answer goes to a file only you can read, and{token}becomes its path. The secret stays out of the settings files.
install asks only for values it hasn't written yet; --ask asks again, for example after a
token change. Without a terminal, set HARNESS_HOOKS_<INPUT>, such as HARNESS_HOOKS_TOKEN.
One command can take several configs, and asks each question once:
uvx harness-hooks install gateway.json langfuse.json otel.json
examples/team-setup is a full setup: an LLM gateway or a subscription, Langfuse traces and metrics.
Your own values stay
harness-hooks changes or removes only the values it set and nobody edited since. It never overwrites a value you set yourself, and tells you when one differs from the config.
install drops the values a config no longer lists, with the same care. A value that two
configs set stays until both are removed.
Settings go only to your own files: --project refuses a config with env, settings or
inputs.
Copies
With files in the config, install copies those files, so the hooks keep working when the
config's folder moves or is deleted. Run install again to bring in changed files; remove
deletes the copies.
Without files, or with --editable, {dir} is the config's folder, so an edit takes
effect at once. --editable also deletes copies left by an earlier install. --project
never copies.
Where hooks go
By default into ~/.claude/settings.json and ~/.codex/hooks.json, so they run in every
project. CLAUDE_CONFIG_DIR and CODEX_HOME move these files, as they do for the agents.
With --project, into .claude/settings.json and .codex/hooks.json of the git repository
that holds the config. Commit them, and everyone who clones the repository gets the hooks.
{dir} is then found from the repository root, so the hooks work in any clone.
Claude reads them only in sessions started at the repository root. Codex reads them only
after you trust the project.
--agent claude or --agent codex touches only that agent.
Updating and removing
Keep name unchanged when updating or removing a config; it can't hold /. install
replaces that config's hooks, so a hook dropped from the config disappears; hooks you added
yourself stay.
Codex asks you to review new or changed hooks at its next start. It remembers approvals by a hook's place in the file, so removing hooks can make it ask again about the hooks after them.
Development
From a clone, uv tool install --editable . runs the code in the clone, so an edit takes
effect at once.
uv run --no-project --with tomlkit python -m unittest
Metadata
Release files for harness-hooks 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| harness_hooks-0.2.0.tar.gz | 13.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| harness_hooks-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.2 kB
Release files / harness_hooks-0.2.0.tar.gz
| Download URL | harness_hooks-0.2.0.tar.gz |
|---|---|
| Size | 13.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
383722f3e105fc69e337ef52b937fb9ad66bc1fcae2ae587af1a850fa97e4573
|
|
BLAKE2b-256 checksum How to use checksums |
2431bf1e21857310be2d6e1334bce76e80a0ea12fdec4ff2b3e5611100953bf9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / harness_hooks-0.2.0-py3-none-any.whl
| Download URL | harness_hooks-0.2.0-py3-none-any.whl |
|---|---|
| Size | 9.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d036fd14125e8b70d272729b501500b1f68d4a096fbe71cb5fea52d575e1d536
|
|
BLAKE2b-256 checksum How to use checksums |
ae3d5f927b8d967425c97bc8fec318c37c59af65a231ac461ecd74d1fe34c8f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|