Skip to content

Add SentinelCommit: a Claude-powered semantic pre-commit guardrail - #1

Merged
Dripar merged 5 commits into
mainfrom
feat/semantic-pre-commit-guardrail
Sep 19, 2026
Merged

Dripar merged 5 commits into
mainfrom
feat/semantic-pre-commit-guardrail

Conversation

@Dripar

@Dripar Dripar commented Sep 19, 2026

Copy link
Copy Markdown
Owner

What this is

A Git pre-commit hook that reads the staged diff, reasons about it with Claude, and refuses the commit when it finds a runtime hazard a linter structurally cannot see — with a ready-to-paste patch in the terminal.

It targets three defect classes, deliberately narrow:

Hazard Shape
TRANSACTION_FAULT Durable write that can fail partway through with no rollback or compensating action
IDEMPOTENCY_RISK At-least-once handler applying side effects with no de-duplication key
RACE_CONDITION Unsynchronised shared state, or a check-then-act (TOCTOU) window

Style, naming and formatting are explicitly not blocking — the linter already owns those.

Design decisions worth reviewing

Two-tier gate. Tier 1 is deterministic and free: empty diffs, docs, config, images and lockfiles never reach the API. Only code-bearing diffs cost a call.

Fail open, always. Missing key, dead network, timeout, malformed response, crashed interpreter — every failure path exits 0 with a warning. A guardrail that blocks a commit because its own dependency is down trains the team to run --no-verify reflexively, which disables it permanently. Blocking is reserved for the one case where the model actively asserts a hazard.

Structured outputs, not a forced tool call. output_config.format with a JSON Schema guarantees a schema-valid verdict, and composes cleanly with adaptive thinking — which is where the quality on this task actually comes from. No regex or string matching anywhere in the decision path.

Dashboard. A blocked commit leaves no trace in git history, so the tool's most valuable moments are invisible to git log. Verdicts are appended to .git/sentinel-audit.jsonl and dashboard.py joins them against the DAG. Stdlib only — no Flask, no npm, no build step.

Bugs caught while testing this

Both were found by running it, not by reading it:

  1. The hook blocked every commit on Windows. Interpreter selection used command -v python3, which resolves to the Microsoft Store stub — it exists, is executable, prints "Python was not found" and exits non-zero, so Git treated it as a hook failure. Now each candidate is probed, and any exit status other than 0/1 fails open.
  2. Docs-only commits were billed for an API call. os.path.splitext("LICENSE") and splitext(".gitignore") both return an empty extension, so extensionless files and dotfiles fell straight through the zero-token gate. Fixed with a filename allowlist; both cases are now regression-tested.

A third, caught by a cross-check over the examples: event_id is a parameter in the race-condition fixture, so it matched the idempotency rule first and reported the wrong hazard. Rules are now ordered by specificity.

Testing

  • 24 unit tests covering the deterministic gate, all three hazard classifications, and audit-log failure modes
  • Both demo scenarios run end-to-end through a real git commit (reject, then pass)
  • Fail-open verified with no credentials present
  • CI runs the suite on Ubuntu + Windows against Python 3.10 and 3.12

Not tested: the live API path. Every run used SENTINEL_MOCK=1 — there was no API key on the build machine. The request shape is built to the current SDK, but it has never made a real call. That is the first thing a reviewer should exercise.

Try it

pip install -r requirements.txt
export ANTHROPIC_API_KEY="sk-ant-..."
./install.sh

cp examples/transaction_fault_unsafe.py payment_service.py
git add payment_service.py && git commit -m "Add payment deduction"   # refused

git reset
cp examples/transaction_fault_safe.py payment_service.py
git add payment_service.py && git commit -m "Add payment deduction"   # passes

python dashboard.py

@Dripar
Dripar merged commit 4f12232 into main Sep 19, 2026
4 checks passed
@Dripar
Dripar deleted the feat/semantic-pre-commit-guardrail branch September 19, 2026 07:46
Dripar added a commit that referenced this pull request Sep 19, 2026
Add SentinelCommit: a Claude-powered semantic pre-commit guardrail
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant