Claude Code PreToolUse hook that auto-approves known-safe read-only shell commands.
Project description
saferead
A Claude Code PreToolUse hook that
auto-approves known-safe, read-only shell commands — so routine reads
(ls, cat, git status, grep, …) don't interrupt you with permission
prompts.
What it does
saferead inspects each Bash command Claude Code is about to run and returns
one of three verdicts:
- allow — every part of the command is a recognized read-only operation.
- ask — a gated git write (
add/commit/stash) on a protected branch (master/main); you get a confirmation prompt. - abstain — anything it doesn't recognize: it stays silent and lets Claude Code's normal permission flow proceed.
It never denies a command. It only ever removes friction (allow) or adds a confirmation (ask), so it composes safely alongside any separate command-blocking layer you run.
Compound commands are decomposed at top-level &&, ||, ;, |, and
newlines (quote / backtick / $(…)-aware). A command is allowed only when
every segment is a recognized safe read.
Design guarantee
The cardinal rule is zero false-allows: a command that mutates state must never be silently approved. Coverage is secondary — when in doubt, it abstains. The hook is also built to never crash or block Claude Code: any internal error is caught and the hook abstains.
Examples
| Command | Verdict | Why |
|---|---|---|
ls -la, cat README.md, grep -rn foo src |
allow | read-only |
git status, git log --oneline, git diff |
allow | read-only git |
cat a.txt && wc -l a.txt |
allow | every segment is a safe read |
git commit -m "wip" on main |
ask | gated write on a protected branch |
git add . on a feature branch |
allow | gated write off a protected branch |
rm -rf build |
abstain | not recognized → normal prompt |
npm install |
abstain | not a read |
cat a.txt && rm b.txt |
abstain | one segment mutates state |
Only the Bash tool is inspected; every other tool is left untouched.
Install
Install the saferead command with uv:
uv tool install saferead
Enable the hook
Register the hook in your Claude Code settings:
saferead install # prompts for global or project settings.json
saferead install --project # writes to ./.claude/settings.json
This writes a PreToolUse entry (matcher Bash) whose command is the full
path to the installed saferead binary — a direct exec on the hot path, with
no launcher overhead. The target settings.json is backed up first and any
existing hook entries are left untouched; re-running is idempotent. The entry
looks like:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "/home/you/.local/bin/saferead" } ] }
]
}
}
Configuration
Configuration is optional — saferead ships with safe built-in defaults.
Two TOML files are read, both optional:
| File | Trust | Purpose |
|---|---|---|
~/.config/saferead/config.toml |
trusted | your global settings |
$CLAUDE_PROJECT_DIR/.claude/saferead.toml |
untrusted | per-project overrides |
The project file can only narrow trust (add protected branches, disable recognizers) — it can never broaden the allow-set for git writes, by design.
Every key is optional; the values below are the built-in defaults:
[git]
protected_branches = ["master", "main"] # ask on a gated git write on these branches
gated_subcommands = ["add", "commit", "stash"] # which git writes are gated
[recognizers]
disabled = [] # recognizer tags to turn off (those commands then abstain)
[python]
allowed_modules = ["math", "datetime", "json"] # modules the `python -c` analyzer admits
allowed_methods = [] # extra read-only methods to admit
[read]
local_allowed_roots = [] # empty = allow any path; non-empty restricts file reads to these roots
ssh_allowed_roots = []
[logging]
enabled = true # audit log of every allow/ask decision (ON by default)
path = "/tmp/claude-hook-audit.log" # where that audit log is written
Logs
saferead writes two files under /tmp:
- Audit log (
/tmp/claude-hook-audit.log) — one JSON line per allow/ask decision. On by default; configure or disable it via the[logging]table above. - Error log (
/tmp/claude-hook.log) — internal diagnostics if the hook ever hits an error (it always abstains rather than crashing). Fixed path, not configurable.
Updating
uv tool upgrade saferead
License
Project details
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 saferead-0.1.3.tar.gz.
File metadata
- Download URL: saferead-0.1.3.tar.gz
- Upload date:
- Size: 160.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d722b9b4a4c7051d800470adf2b887bcb557375759283da33a43d65ef772e3c
|
|
| MD5 |
62f810f62024c93cf83cd38b928fb646
|
|
| BLAKE2b-256 |
9e2b5ad07e443a09c6f6b09a12f94fcc8f49feebb37fb042ad2d43f580ef7bb1
|
Provenance
The following attestation bundles were made for saferead-0.1.3.tar.gz:
Publisher:
release.yml on tovmeod/saferead
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
saferead-0.1.3.tar.gz -
Subject digest:
9d722b9b4a4c7051d800470adf2b887bcb557375759283da33a43d65ef772e3c - Sigstore transparency entry: 1886670440
- Sigstore integration time:
-
Permalink:
tovmeod/saferead@c3f37e84d5e5dc917c18cd07462996657cc1dbd7 -
Branch / Tag:
refs/tags/0.1.3 - Owner: https://github.com/tovmeod
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c3f37e84d5e5dc917c18cd07462996657cc1dbd7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file saferead-0.1.3-py3-none-any.whl.
File metadata
- Download URL: saferead-0.1.3-py3-none-any.whl
- Upload date:
- Size: 110.8 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 |
c7fa26aee901b0e072e3d5d173e1729ac8e59426ba4d34164836719c800485c3
|
|
| MD5 |
1e3ca128dbf6c7015507d5936cda8257
|
|
| BLAKE2b-256 |
a44a4c4248884a80b3e1cb1e627b084a77c873e6e7d72dd977a653026d238e2a
|
Provenance
The following attestation bundles were made for saferead-0.1.3-py3-none-any.whl:
Publisher:
release.yml on tovmeod/saferead
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
saferead-0.1.3-py3-none-any.whl -
Subject digest:
c7fa26aee901b0e072e3d5d173e1729ac8e59426ba4d34164836719c800485c3 - Sigstore transparency entry: 1886670454
- Sigstore integration time:
-
Permalink:
tovmeod/saferead@c3f37e84d5e5dc917c18cd07462996657cc1dbd7 -
Branch / Tag:
refs/tags/0.1.3 - Owner: https://github.com/tovmeod
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c3f37e84d5e5dc917c18cd07462996657cc1dbd7 -
Trigger Event:
release
-
Statement type: