Wiring an LLM into your commit workflow saves mental overhead without sacrificing quality. This tutorial builds a small tool for bash git commit message llm automation that captures your staged diff, sends it to an OpenAI-compatible chat endpoint, and prints a ready-to-use commit message. You’ll end with a reusable script you can bind to a git alias.
Prerequisites
- Bash 4+ (uses
jqfor JSON parsing) gitinstalled with a repo containing staged changescurlandjqavailable on PATH- An API key for any OpenAI-compatible endpoint. If you point the script at n4n.ai, you get automatic fallback when a provider is rate-limited and per-token metering without extra code.
Export these variables before running anything:
export LLM_API_KEY="sk-..."
export LLM_BASE_URL="https://api.openai.com/v1" # or your gateway
export LLM_MODEL="gpt-4o-mini"
Step 1: Capture the staged diff
Commit messages should describe what you staged, not the entire working tree. Use git diff --cached (alias --staged).
diff=$(git diff --cached --no-color)
if [ -z "$diff" ]; then
echo "No staged changes. Run git add first." >&2
exit 1
fi
Expected output when nothing is staged:
No staged changes. Run git add first.
If you staged a one-line change to README.md, echo "$diff" looks like:
diff --git a/README.md b/README.md
index 3a1b2c..4d5e6f 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
# My Project
-Old description.
+New description with more detail.
Step 2: Call the LLM API
The core of bash git commit message llm automation is a curl POST to /chat/completions. We send a strict system prompt and the diff as a user message. Conventional Commits format keeps history clean.
prompt_system="You write git commit messages. Output only the message, no quotes. Use Conventional Commits format: type(scope): subject. Add a blank line then body if the diff is non-trivial."
payload=$(jq -n \
--arg sys "$prompt_system" \
--arg diff "$diff" \
'{model: env.LLM_MODEL, messages: [{role:"system", content:$sys},{role:"user", content:$diff}], temperature:0.2, max_tokens:200}')
response=$(curl -sS "$LLM_BASE_URL/chat/completions" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d "$payload")
A successful response is standard OpenAI JSON:
{
"choices": [
{
"message": {
"content": "docs(readme): clarify project description\n\nReplaced vague old description with a specific line explaining the project."
}
}
]
}
Step 3: Parse and sanitize
Extract the message with jq. Strip whitespace and accidental markdown code fences.
msg=$(echo "$response" | jq -r '.choices[0].message.content // empty')
if [ -z "$msg" ]; then
echo "LLM returned empty response. Check API limits or model." >&2
exit 2
fi
msg=$(echo "$msg" | sed 's/^```//;s/```$//' | sed '/^$/N;/^\n$/D')
Print it for review before committing:
echo "----- SUGGESTED COMMIT MESSAGE -----"
echo "$msg"
echo "------------------------------------"
Expected terminal output:
----- SUGGESTED COMMIT MESSAGE -----
docs(readme): clarify project description
Replaced vague old description with a specific line explaining the project.
------------------------------------
Step 4: Wrap it in a reusable script
Combine the pieces into git-commit-msg.sh. Make it executable. Add a --commit flag to actually run git commit.
#!/usr/bin/env bash
set -euo pipefail
MODEL="${LLM_MODEL:-gpt-4o-mini}"
BASE_URL="${LLM_BASE_URL:-https://api.openai.com/v1}"
API_KEY="${LLM_API_KEY:?Set LLM_API_KEY}"
DO_COMMIT=0
if [ "${1:-}" = "--commit" ]; then DO_COMMIT=1; fi
diff=$(git diff --cached --no-color)
if [ -z "$diff" ]; then
echo "No staged changes. Run git add first." >&2
exit 1
fi
sys_prompt="You write git commit messages. Output only the message, no quotes. Use Conventional Commits format."
payload=$(jq -n --arg sys "$sys_prompt" --arg diff "$diff" \
'{model: env.MODEL, messages:[{role:"system",content:$sys},{role:"user",content:$diff}], temperature:0.2, max_tokens:200}')
resp=$(curl -sS "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "$payload")
msg=$(echo "$resp" | jq -r '.choices[0].message.content // empty')
if [ -z "$msg" ]; then
echo "Empty LLM response. Aborting." >&2
exit 2
fi
msg=$(echo "$msg" | sed 's/^```//;s/```$//')
if [ "$DO_COMMIT" -eq 1 ]; then
git commit -m "$msg"
else
echo "$msg"
fi
Run without committing to inspect:
./git-commit-msg.sh
Then commit for real:
./git-commit-msg.sh --commit
The second command creates the commit silently. Verify with git log -1.
Step 5: Handle real-world edge cases
Production-grade bash git commit message llm automation needs to survive API hiccups. Capture HTTP status and retry on transport failure.
http_code=$(curl -sS -o /tmp/resp.json -w "%{http_code}" "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "$payload")
if [ "$http_code" != "200" ]; then
echo "API error HTTP $http_code: $(jq -r '.error.message // empty' /tmp/resp.json)" >&2
exit 3
fi
resp=$(cat /tmp/resp.json)
If you use a gateway that honors client routing directives, you can pin a provider by sending extra_headers or a model suffix; n4n.ai forwards provider cache-control hints so repeated diffs with stable prefixes cost less. That is an optional optimization once the script works.
Guard against huge diffs. Truncate to first 8000 characters:
if [ "${#diff}" -gt 8000 ]; then
diff="${diff:0:8000}
... (truncated)"
fi
Add a git alias in ~/.gitconfig:
[alias]
ai-commit = "!f() { ./git-commit-msg.sh --commit; }; f"
Now git ai-commit uses whatever you already staged.
Test in a scratch repo
Validate the flow without touching real work:
mkdir /tmp/test-repo && cd /tmp/test-repo
git init -q
echo "hello" > file.txt
git add file.txt
echo "world" >> file.txt
git add file.txt
/path/to/git-commit-msg.sh
Expected message resembles:
chore: append world to file.txt
Run with --commit and git log -1 shows the message applied.
Closing notes
The script above is deliberately minimal. You can extend it to read .gitignore patterns, support --amend, or cache diff embeddings. The important part is that bash git commit message llm automation is just a curl call plus a diff—no heavy framework required.
Check token usage from your gateway’s metering to tune max_tokens. If the model drifts from Conventional Commits, tighten the system prompt and drop temperature to 0.1.