commitclerk logo
Apache 2.0 · zero dependencies · one file you can read

Stop writing
“fix stuff”.

A clerk records what actually happened. commitclerk reads your staged diff, asks an LLM for a Conventional Commits message, shows it to you, and commits — in a single Python file with no third-party packages.

📦 pipx install commitclerk 🐍 Python 3.8+, stdlib only 🔗 Works as git clerk
~1 100 lines · one file Conventional Commits 1.0.0 Any OpenAI chat model Apache 2.0
~/your-repo
$ git add .
$ clerk

--- commit message ---
fix: prevent duplicate webhook deliveries on retry

- Deduplicate by delivery id before enqueueing, so a provider retry no
  longer fans out into multiple downstream jobs.
- Store the id in the existing idempotency table instead of a new one,
  keeping the retention policy in a single place.
- Log a debug line on the dedupe path to make retry storms visible.
----------------------
[main a1b2c3d] fix: prevent duplicate webhook deliveries on retry

Titles stay imperative and under 72 characters. Bodies stay 2–6 bullets about why — never a file-by-file replay. No emojis, no headers, no code fences.

Why you'll love it

Small enough to trust, opinionated enough to be useful

One downloadable file, no dependencies — you can read the whole thing before letting it near your source code. (The source is a small package; the file is a build of it.)

🪶

Zero dependencies

Standard library only — urllib, subprocess, argparse. Drop the file in and run it, or install it properly. Your call.

🚫

Per-file veto, not per-repo

A .clerkignore — same syntax as .gitignore — withholds a file's contents: the model gets its name, its line counts and a placeholder. Three sensitive files no longer mean banning the tool everywhere.

✈️

Works with the network down

--offline writes the message locally — no API call, no key, no model. Type from the file classes, bullets grouped by directory. It never guesses feat: or fix:, so it's a draft, not a replacement.

🛡️

Refuses to leak a secret

Stage a .env by accident and the request goes out before the commit exists. Known key shapes and high-entropy tokens stop the run with exit 3 — nothing sent, the file and line named, the secret never printed.

🔗

Git-native

Installs as git clerk too, so it lives where the rest of your git muscle memory already is: git add -A && git clerk.

✍️

You can own the title

-m "feat: add X" uses your title verbatim and reduces the model's job to writing the body. The framing of the commit stays yours.

📄

Doc-aware

Detects documentation-only commits and refuses to describe already-shipped features as new work — the blind spot every diff-only generator has.

👀

Dry run

--dry-run prints the message and commits nothing. Look before you leap, every time, at no risk to your history.

🔧

Model agnostic

OpenAI, Anthropic or a local Ollama model via --provider, any model via --model, and any OpenAI-compatible endpoint via --base-url — LM Studio, vLLM, OpenRouter, Groq, Azure. Cheap by default, stronger when the change is subtle, local when the diff must stay put.

⚙️

Config file

A committed .clerk.json carries the team's provider, model and budgets with the repo, so the convention stops being flags everyone retypes. Flag beats environment beats project file beats your own ~/.config defaults — one order, written down, no surprises.

The blind spot

Most generators only see the diff

When a commit adds prose to a CHANGELOG, ROADMAP or README describing a feature that shipped three commits ago, a diff-only generator reads that prose and announces the feature — for a commit that changed nothing but Markdown. Your history is now lying to you, and git log --grep and your release tooling inherit the lie.

Diff-only generator
feat: implement real-time collaboration

…for a commit that only added a paragraph to the changelog. Nothing was implemented. The next release notes will claim otherwise.

commitclerk
docs: record real-time collaboration in the changelog

Every staged file is documentation, so the prompt switches to a docs-only framing: describe the documentation change itself, never “implement X”.

Two safeguards

Detection, plus an override you control

  • 1Documentation detection. If every staged file is docs — .md, .mdx, .rst, .txt, .adoc, anything under docs/, or a known name like CHANGELOG / README / ROADMAP — the framing switches to docs:. When docs are mixed with code, the type comes only from the non-documentation lines, so a docstring tweak beside a big changelog entry is still docs:.
  • 2-m as an override. You know what your change is. Pinning the title reduces the model's job to summarizing the diff underneath it — the recommended default for any commit whose intent isn't obvious from the diff alone.
How it works

Four steps, no magic

git diff --staged ├─▶ truncate to --max-chars ├─▶ doc-only? → switch framing ├─▶ build prompt → Chat Completions └─▶ print git commit -F -

The whole thing is ~230 lines. It's meant to be read, forked, and adapted to your team's conventions — start with the _RULES string.

Command line

A handful of flags, and you'll mostly use one

Installing gives you three identical entry points: clerk, commitclerk, and git clerk.

clerk [-m TITLE] [--dry-run] [--provider NAME] [--base-url URL] [--model MODEL] [--timeout S] [--max-chars N] [--version]
FlagDefaultWhat it does
-m, --message TITLEUse TITLE verbatim as the commit title; the AI writes only the body bullets.
--dry-runoffPrint the generated message and exit without committing.
--provider NAMEopenaiopenai, anthropic, or ollama (local, no key) — or set $CLERK_PROVIDER.
--base-url URLhttps://api.openai.com/v1Any OpenAI-compatible endpoint — Ollama, LM Studio, vLLM, OpenRouter, Groq, Azure — or set $OPENAI_BASE_URL.
--model MODELgpt-4o-miniModel to call — the provider's default, or set $OPENAI_MODEL.
--timeout S60Seconds to wait for each API request. Raise it for a slow local model; transient failures are retried twice with backoff.
--max-chars N60000Character budget for the diff. A larger diff is trimmed per file, so every changed file still reaches the model; lockfiles and vendored files collapse to one line first.
--redactoffWhen the pre-flight scan finds a suspected secret, mask it in the request and carry on instead of refusing. The commit still contains it — this protects what is sent, not what is committed.
--no-scanoffSkip the pre-flight secret scan entirely.
--offlineoffWrite the message locally — no API call, no key, no network. Never emits feat: or fix:, so treat it as a draft.
--versionPrint the version and exit.

Examples

# the AI writes the whole message clerk # you choose the title — the most reliable mode clerk -m "refactor: extract retry policy" # preview only, never commits clerk --dry-run # a stronger model for a subtle change clerk --model gpt-4o # a local model — no API key, nothing leaves your machine clerk --provider ollama # Anthropic instead of OpenAI clerk --provider anthropic

Exit codes

0Committed (or --dry-run printed the message).
1Nothing staged — run git add first.
2Configuration problem — the provider's API key is not set, or the provider does not exist.
3A suspected secret in the staged diff — nothing was sent.
otherPassed through from git commit.

Scriptable on purpose: the exit code tells your wrapper exactly what happened.

Get started

Up and running in one minute

Three steps, and the third one is just git add.

1

Install it

pipx install commitclerk — or download the single file and run python commitclerk.py. Both work identically.

2

Set your API key

export OPENAI_API_KEY="sk-…" (on Windows, setx — then reopen the terminal).

3

Stage and commit

git add . then clerk --dry-run to look, clerk to commit.

Install from PyPI:

pipx install commitclerk

Or skip installing entirely — it's one file with no dependencies:

curl -O https://raw.githubusercontent.com/alegauss/commitclerk/main/dist/commitclerk.py
📦 commitclerk on PyPI
Optional

Stage-and-commit in one word

Two wrappers ship with the repo — run-commit.cmd (Windows) and run-commit.sh (macOS / Linux). Both check the API key, stage everything with git add -A, then pass your arguments straight through.

Heads up: they stage everything, including new, deleted and dot-prefixed files. The Python script itself never stages anything on its own.

REM Windows run-commit.cmd -m "feat: add CSV export" # macOS / Linux ./run-commit.sh -m "feat: add CSV export" # or just an alias alias ac='git add -A && clerk'
🔒

Know what leaves your machine

A .clerkignore keeps chosen files' contents off the wire entirely. Same syntax as .gitignore, at the repository root. A matched file reaches the model as its path, its line counts and a placeholder — never its body. The paths themselves are still sent; if a filename cannot be disclosed either, use --offline.

Nothing is sent until the staged diff has been scanned for secrets. Every added line is checked against known credential shapes and a high-entropy heuristic before the first request. A hit stops the run with exit 3 and names the file and line — never the secret itself. This runs on the diff as staged, before any trimming, so there is no ordering in which something is sent and then checked.

Your staged diff is sent to the API you configuredapi.openai.com by default, or api.anthropic.com with --provider anthropic. That is the whole point, and it is the one thing to be deliberate about. On a repository whose contents cannot leave your machine, run --provider ollama — a local model, no API key, nothing over the network — or don't run the tool there at all. Check your employer's policy first.

Nothing else is transmitted, stored, or logged by this tool — no telemetry, no analytics, no remote config. The API key is read from the environment and never written to disk. Cost is a single Chat Completions call per commit: with the default gpt-4o-mini and a typical diff, a fraction of a cent.

Open source

Good first contributions are on the roadmap

Keep it dependency-free, keep it one file, and open an issue before a large change. Grab one of these, or propose your own.

🪝

prepare-commit-msg hook

An installer so the message is drafted by git itself, wherever you commit from.

🧩

More providers

Anthropic, Azure OpenAI, and Ollama / local models — without breaking the zero-dependency rule.

📝

Interactive --edit

Open the generated message in $EDITOR before the commit lands.

clerk --lint

Validate an existing message with no API call — usable as a commit-msg hook.

🎬

A demo cast

An asciinema recording or GIF for the top of the README. Worth a thousand words.

Read CONTRIBUTING.md