Secure Your Git Workflow in Claude Code: Block Secrets & Enforce Commit Standards

Installation

This workflow combines three Claude Code Templates components. Install all three with one command:

npx claude-code-templates@latest \
  --hook security/secret-scanner \
  --hook git/conventional-commits \
  --skill security/secrets-management

This installs both hook scripts into .claude/hooks/, registers them in .claude/settings.json, and adds the skill to .claude/skills/.

Want to understand how it works? Keep reading to see how these three components combine into a single, deterministic git safety net — and why one of them is deliberately not deterministic.

The Problem: Two Different Ways a Commit Goes Wrong

Most "bad commit" incidents fall into one of two buckets:

  • A secret slips in — an API key, a database URL, a token pasted in during a debugging session — and gets committed before anyone notices.
  • The history turns to noise — commit messages like "fix" or "wip" that make changelog generation and semantic versioning impossible to automate.

Asking Claude nicely in CLAUDE.md to "never commit secrets" or "always use conventional commits" helps, but it's a suggestion Claude can miss under pressure. Hooks turn both rules into code that runs on every single git commit, no exceptions.

How the Hooks Actually Intercept a Commit

Both hooks register on the PreToolUse event with a matcher for the Bash tool. Every time Claude Code is about to run a Bash command, it sends the tool call as JSON on the hook script's stdin:

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git commit -m \"add login form\"",
    "description": "Commit the new login form"
  },
  "tool_use_id": "...",
  "cwd": "/path/to/project"
}

The hook script inspects tool_input.command, and if it isn't a git commit call, exits immediately with code 0 so the command runs unmodified. If it is a commit, the script decides whether to allow or deny it — and it can deny in two equivalent ways: exiting with code 2 (stderr becomes the reason shown to Claude), or exiting 0 while printing a JSON object with permissionDecision: "deny". Both hooks in this workflow use the JSON form:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "❌ Invalid commit message format..."
  }
}

Because secret-scanner and conventional-commits both match the Bash tool, Claude Code registers them as two independent entries in the PreToolUse array — installing the second doesn't overwrite the first, it appends to it. Every Bash call runs both, and if either one denies, the commit is blocked. A leaked key and a malformed message are caught by the same event, by two hooks that never have to know about each other.

1. secret-scanner — Catch Credentials Before They Commit

The scanner matches the outgoing command against 30+ provider-specific patterns — not generic "looks like a password" heuristics, but the actual token shapes providers issue:

SECRET_PATTERNS = [
    (r'AKIA[0-9A-Z]{16}', 'AWS Access Key ID', 'high'),
    (r'sk-ant-api\d{2}-[A-Za-z0-9\-_]{20,}', 'Anthropic API Key', 'high'),
    (r'sk-[a-zA-Z0-9]{48,}', 'OpenAI API Key', 'high'),
    (r'AIza[0-9A-Za-z\-_]{35}', 'Google API Key', 'high'),
    (r'sk_live_[0-9a-zA-Z]{24,}', 'Stripe Live Secret Key', 'critical'),
    (r'ghp_[0-9a-zA-Z]{36}', 'GitHub Personal Access Token', 'high'),
    (r'hf_[a-zA-Z0-9]{34,}', 'Hugging Face Token', 'high'),
    # ...and 20+ more: GitLab, Vercel, Supabase, Replicate, Groq, Databricks, Azure...
]

When a match is found in the staged diff, the hook denies the commit and tells Claude exactly what was matched and why — nudging it toward environment variables instead of silently stripping the line.

2. conventional-commits — Keep the History Machine-Readable

This hook only activates on commands containing git commit, extracts the message from the -m flag (or a heredoc-style multi-line message), and checks it against the Conventional Commits grammar:

type(scope): description

Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert

A message like "fix" or "updated stuff" gets denied with the exact rule and two or three corrected examples in the reason string, so Claude can immediately retry with fix: resolve memory leak in parser instead of guessing.

Commit attempt Result
git commit -m "fix" ❌ Denied — missing description
git commit -m "Added new feature" ❌ Denied — no type: prefix
git commit -m "feat(auth): add JWT refresh" ✅ Allowed

3. secrets-management — The Skill That Fills the Gap the Hooks Leave Open

Here's the part that trips people up: a hook can only react to a hardcoded secret that's already been typed. It can't tell you where that secret should have lived instead. That's not a job for a deterministic script — it depends on your stack, your CI provider, and whether you're rotating credentials or provisioning them for the first time. That's exactly the kind of judgment call a Skill is built for.

Hooks vs. Skills — different trigger models, on purpose: A hook is event-driven and deterministic — the harness fires it on every matching tool call, unconditionally. A skill has no hooks field in its frontmatter; instead, Claude reads its description and decides whether it's relevant to the current conversation, the same way it decides whether to reach for any other piece of context. Hooks enforce; skills advise.

So when secret-scanner blocks a commit containing a Stripe key, Claude has the secrets-management skill available to reach for — and its guidance is specific per backend:

# AWS Secrets Manager
aws secretsmanager create-secret \
  --name production/database/password \
  --secret-string "super-secret-password"

# HashiCorp Vault
vault kv put secret/database/config username=admin password=secret

# GitHub Actions
echo "API Key: ${{ secrets.API_KEY }}"

The skill also documents rotation, least-privilege access, and masking secrets in CI logs — the practices that keep a fixed leak from becoming a recurring one.

The Workflow, End to End

Here's what actually happens when you ask Claude Code to commit a change that contains a hardcoded key:

1. Claude runs git commit -m "feat: add stripe integration"

2. Both PreToolUse hooks fire in parallel. conventional-commits allows the message — it's well-formed. secret-scanner finds sk_live_... in the diff and denies.

3. The commit is blocked. Claude receives the scanner's reason and, with the secrets-management skill in context, replaces the hardcoded key with process.env.STRIPE_SECRET_KEY and adds it to .env (already gitignored).

4. Claude retries the commit. This time both hooks allow it, and it lands in your history clean — no secret, properly typed message.

Nobody had to review a diff to catch the leak, and nobody had to fix the commit message after the fact. The two hooks make the enforcement automatic; the skill makes the fix correct.

Where Each Piece Belongs

Component Type Trigger Job
secret-scanner Hook Every git commit Deny commits containing credential patterns
conventional-commits Hook Every git commit Deny non-conventional commit messages
secrets-management Skill Claude's judgment Guide the fix once a secret is caught
Rule of thumb: If a rule must never be violated, put it in a hook — it runs whether Claude "remembers" or not. If a rule requires context-dependent judgment, a skill is the right shape, because it works alongside the hook rather than trying to replace it.

Conclusion

None of these three components is complicated on its own. What makes this a workflow rather than three separate installs is that they cover each other's blind spots: the scanner catches the leak, the commit-message hook keeps history usable, and the skill supplies the fix the hooks can't compute. Install all three and your git workflow gets a safety net that works the same way at 2am as it does in a careful code review.

Explore 800+ Claude Code Components

Discover agents, commands, MCPs, settings, hooks, skills and templates to supercharge your Claude Code workflow

Browse All Components

Created by Daniel Ávila

Back to Blog