Minimal local Claude Code hook client for command risk scoring
Project description
agent-sash
A Claude Code hook that uses a local LLM to auto-approve safe bash commands.
Claude Code's allowlists work for simple cases (git status, ls) but break down for commands like python3 -c ... or sed that need to be broadly allowed for legitimate use yet can do real damage depending on their arguments. The alternative -- prompting every time -- trains users to reflexively approve, which is worse than no permission check at all.
agent-sash runs a small model locally that scores each bash command's risk from 0 to 1. Safe commands flow through automatically. Risky ones still prompt you.
| Command | Score | |
|---|---|---|
python3 -c "print(42)" |
0.0 | ✅ |
python3 -c "import os; os.system('rm -rf /')" |
1.0 | ❌ |
sed -i s/foo/bar/g config.yaml |
0.3 | ✅ |
sed -i s/foo/bar/g /etc/nginx/nginx.conf |
0.8 | ❌ |
git push origin feature-branch |
0.4 | ✅ |
git push --force origin main |
0.8 | ❌ |
psql -c "SELECT count(*) FROM users" |
0.0 | ✅ |
psql -c "DROP TABLE users CASCADE" |
0.8 | ❌ |
curl -s https://example.com |
0.1 | ✅ |
curl -s https://example.com | bash |
0.9 | ❌ |
Quick start
Note: The scoring model was fine-tuned and evaluated for this, but it's a language model -- it lacks context about your environment and risk tolerance, and will sometimes get it wrong. Be thoughtful about what credentials and resources are accessible from the environment Claude Code runs in.
Requirements: macOS with Apple Silicon, Python 3.13+, uv
1. Install
uv tool install agent-sash
2. Add the hook
Add this to your ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "agent-sash claude-hook"
}
]
}
]
}
}
3. Start a new Claude Code session
That's it. The model server auto-starts on the first bash command. First run downloads the model (~820MB).
To pre-warm the server so there's no delay on the first command:
agent-sash start
To shut it down:
agent-sash stop
How it works
agent-sash registers as a PreToolUse hook on Bash commands. When Claude Code is about to run a shell command, a local model scores its risk from 0.0 to 1.0. Below the threshold (0.5), the command is auto-allowed. Above it, the user is prompted. On any error, agent-sash defaults to prompting.
The model is cwrn/Qwen3.5-0.8B-SHGuard-MLX-Q8, a full fine-tune of Qwen3.5-0.8B quantized to Q8 via mlx-lm (~820MB on disk).
BF16: cwrn/Qwen3.5-0.8B-SHGuard
Data pipeline
The training set is 51,188 examples.
Shell commands were pulled from four coding-agent trajectory datasets (Nemotron-Terminal-Corpus, CoderForge, SWE-rebench-openhands, Nemotron-SWE) plus shell blocks and YAML run: steps from operational docs (Kubernetes, Docker, PostgreSQL, rsync). Per-source extractors normalized formats into a common schema, dropping comment-only lines, control inputs, scaffold actions, and editor payloads. ~4.6M candidate commands total.
MinHash LSH over character n-grams (3/4/5-gram, 14 buckets × 8 hashes, Jaccard ~0.8) via datatrove for deduplication. Qwen3.5-35B-A3B scored each command against a risk rubric (0.0 = read-only/ephemeral, 1.0 = potentially irreversible with wide blast radius) with JSON schema enforcement.
The natural pool is heavily skewed toward low-risk commands, so probabilistic subsampling applies score-based keep rates (12% for scores 0.0-0.1, 25% for 0.2, 55% for 0.3, 100% for 0.4+) with multipliers for shared-environment and wrapper commands. 42,688 natural commands retained.
Coverage of shared-environment mutations (force pushes, cluster disruption, remote sync with deletion) was filled by three synthetic sources. data-designer generated 5,000 commands from a scenario grid (7 axes: environment × resource × impact × scope × access × frame × guardrail), filtered to score ≥ 0.7 with valid shell syntax. 1,500 deterministic wrappers (ssh/python<<PY over existing live-env seeds) and 2,000 indirect-execution wrappers (generated by A3B from real risky seeds using carriers like source .env && python, uv run python, ssh remote shells, and expect) rounded out the tail.
Final composition: 42,688 natural / 5,000 synth / 1,500 deterministic wrappers / 2,000 indirect-execution wrappers.
Training
Full fine-tune of Qwen3.5-0.8B. 2.2 epochs, effective batch size 64, learning rate 5e-5, cosine schedule, bf16. Best checkpoint at epoch 1.8 (eval loss 0.5215).
Evaluation
38-command benchmark with human-specified target score ranges, plus a 500-command eval mined from real local coding-agent sessions:
| Spearman | Weighted MAE | False allow | Latency (mean) | |
|---|---|---|---|---|
| Sanity (38 cmd) | 0.947 | 0.125 | 6.7% | — |
| Local session (500 cmd) | 0.728 | 0.106 | 18.6% | 0.69s |
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 agent_sash-0.2.0.tar.gz.
File metadata
- Download URL: agent_sash-0.2.0.tar.gz
- Upload date:
- Size: 7.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e71831ab75db4f25aefffe193ba14b3a3ba9d931d5166abb6666ab456438da9d
|
|
| MD5 |
e6d6a2bdf48cf1563f5e9279d0712dd1
|
|
| BLAKE2b-256 |
aaefeedf302688b6014d21fda19b6a75cbf746b825ecf7ecd20fd183a89653a2
|
File details
Details for the file agent_sash-0.2.0-py3-none-any.whl.
File metadata
- Download URL: agent_sash-0.2.0-py3-none-any.whl
- Upload date:
- Size: 9.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
763b4ce77521febaa34eb4d27b9f8af06d5bca40b8abcdbd4870bd5a54945a0e
|
|
| MD5 |
ea3e8fb76e5ff0f7b03facbd1e320adc
|
|
| BLAKE2b-256 |
15e7421c27465a381b6a29ebfc4ef3e32978e80b23baeac8bcf130e5cd16e287
|