Tutorials & Guides
How to build a custom CLI tool that uses Claude to write your Git commits
Stop writing lazy, single-word commit messages. Build a lightweight, local CLI tool that uses Claude to analyse your staged diffs and write perfectly formatted, semantic commits.
Updated 8/16/2026
Let us be honest for a moment. Nobody actually enjoys writing commit messages. When you are deep in the zone, flowing between files and solving complex problems, the last thing you want to do is halt your momentum to carefully craft a poetic summary of your changes. Usually, this results in lazy, half-hearted commits like fixed bug or updates.
But bad commit histories are a technical debt nightmare. If you want to keep your repository history pristine without losing your creative stride, you can automate this chore.
In this tutorial, we will build a custom Command Line Interface (CLI) tool that hooks directly into your Git workflow. It will extract your staged changes, send them to Claude's API, and return a perfectly formatted, semantic commit message. Best of all, it takes fewer than 100 lines of Python and runs locally in seconds.
Why build your own instead of using a bloated extension?
There are dozens of VS Code extensions and pre-packaged NPM packages that promise to do this. The problem? They are often bloated, demand invasive permissions to your entire codebase, or force you into their proprietary subscription models.
Building your own tool means you control the prompt, you choose the model, and you only pay for the raw tokens you actually use. We will use the Anthropic API via Python to target Claude 3.5 Sonnet—which is currently the gold standard for parsing code structures and understanding developer intent. You can learn more about configuring this model on our /platforms/claude hub.
Prerequisites
Before we write any code, you will need: - Python 3.8 or higher installed on your machine. - An Anthropic API key with a small amount of credit. - Git installed and configured.
Step 1: Capturing the staged git diff
To write an accurate commit message, our tool needs to know exactly what changed. We do not want to analyse the entire repository; we only want to look at the files you have explicitly staged using git add.
We can retrieve this programmatically using Python’s built-in subprocess module to run git diff --staged.
Create a new file named gcommit.py and start with this foundation:
`python
import subprocess
import sys
import os
from anthropic import Anthropic
def get_staged_diff():
try:
# Run git diff for staged changes
result = subprocess.run(
["git", "diff", "--staged"],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError:
print("Error: Are you sure this is a Git repository?")
sys.exit(1)
`
If there are no staged changes, sending an empty request to the API is a waste of your precious rate limits. Let us add a quick safety check inside our main execution block:
`python
diff = get_staged_diff()
if not diff:
print("No staged changes found. Use 'git add' to stage your files first.")
sys.exit(0)
`
Step 2: Crafting the system prompt
This is where we define how our commits should look. To keep our Git history organised, we will enforce the Conventional Commits specification (e.g., feat(auth): add JWT validation).
We need to explicitly instruct Claude to return only the raw commit message. If the model starts explaining its reasoning or adding polite conversational filler, our automation will break. You can brush up on these structural constraints in our prompts generator guide.
Here is our system prompt variable:
`python
SYSTEM_PROMPT = """
You are an expert developer's assistant. Your sole task is to write a concise, professional Git commit message based on the provided 'git diff' output.
You must strictly adhere to the Conventional Commits specification. Use one of these prefixes: - feat: A new feature - fix: A bug fix - docs: Documentation changes - style: Code style changes (formatting, missing semi-colons) - refactor: Code changes that neither fix a bug nor add a feature - test: Adding or correcting tests - chore: Updates to build tasks, package manager configs, etc.
Format requirements: 1. First line: A clear summary in lowercase, maximum 50 characters, starting with the appropriate prefix. 2. Leave one blank line. 3. Body: A bulleted list explaining what was changed and why (not 'how'), with lines wrapped at 72 characters.
Output only the raw commit message. Do not include markdown block formatting, backticks, or introductory text. Just the commit text itself.
"""
`
Step 3: Integrating with the Claude API
Now we will plug in the Anthropic client to handle the generation. Make sure you have installed the SDK by running pip install anthropic in your terminal.
`python
def generate_commit_message(diff_text):
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
print("Error: ANTHROPIC_API_KEY environment variable not set.")
sys.exit(1)
client = Anthropic(api_key=api_key)
try:
message = client.messages.create(
model="claude-3-5-sonnet-latest",
max_tokens=300,
temperature=0.2, # Low temperature for consistent, logical formatting
system=SYSTEM_PROMPT,
messages=[
{
"role": "user",
"content": f"Write a commit message for this diff:\n\n{diff_text}"
}
]
)
return message.content[0].text.strip()
except Exception as e:
print(f"API Error: {str(e)}")
print("For API issues, consult the Anthropic support channels at https://claude-support.com.")
sys.exit(1)
`
Step 4: Hooking it up to Git
To make this run seamlessly, we want our script to print the message, ask for our confirmation, and then run the actual git commit -m command for us.
`python
def main():
diff = get_staged_diff()
if not diff:
print("No staged changes. Stage some files first using 'git add'.")
return
print("Analyzing staged changes and generating commit message...") commit_msg = generate_commit_message(diff) print("\nProposed Commit Message:") print("-" * 40) print(commit_msg) print("-" * 40) confirm = input("Apply this commit message? (y/N): ").strip().lower() if confirm == 'y': try: subprocess.run(["git", "commit", "-m", commit_msg], check=True) print("\nCommit successful!") except subprocess.CalledProcessError as e: print(f"\nFailed to commit: {e}") else: print("\nCommit aborted. No changes were committed.")
if __name__ == "__main__":
main()
`
Step 5: Making it a system-wide alias
To run this script from anywhere without typing python3 ~/path/to/gcommit.py, we can create a quick global alias.
1. Make the Python file executable by adding #!/usr/bin/env python3 to the absolute top of your file.
2. Rename your file to simply git-ai and remove the .py extension.
3. Move the file to a folder in your system path, such as /usr/local/bin:
`bash
chmod +x git-ai
mv git-ai /usr/local/bin/
`
Now, inside any Git repository on your machine, you can simply run:
`bash
git ai
`
Git automatically interprets any command formatted as git-foo in your path as a subcommand.
Managing giant diffs
One small catch: if you accidentally stage a 10,000-line minified Javascript file or an autogenerated lockfile, you will blow past Claude’s input token limit and run up an unnecessary bill.
To prevent this, you can check our /glossary to understand how context windows and token optimization work, or implement a simple check in your python script that rejects diffs containing more than 15,000 characters before calling the API.
With this tool active in your local terminal, you can keep your momentum high and your git history clean—letting AI do the boring chore of tracking what makes your codebase tick.
Keep going
Build something with the prompt generator, decode the jargon in the glossary, or compare the tools on our platform deep-dives.