n4nAI

Generate git commit messages with a bash LLM script

Build a bash script that automates git commit messages with an LLM. Step-by-step tutorial covering diff capture, API calls, parsing, and error handling.

n4n Team2 min read448 words

Audio narration

Coming soon — every post will get a voice note here.

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 jq for JSON parsing)
  • git installed with a repo containing staged changes
  • curl and jq available 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.

Tagsbashgitautomationllm-api

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All bash/shell scripting llm automation posts →