Write your git commit messages with AI.
lazycommit reads the diff you have staged, asks an AI to describe it, shows
you the message it came up with, and commits only if you accept it. It replaces
the part of git commit -m "..." where you stare at the screen trying to
remember what you just did.
$ lazycommit
3 files changed, 87 insertions(+), 12 deletions(-)
┌─ Suggested commit ──────────────────────────────────────────┐
│ feat(auth): rotate refresh tokens on every use │
│ │
│ Refresh tokens were long lived, so a leaked token stayed │
│ valid until it expired. Each refresh now issues a new │
│ token and invalidates the previous one. │
│ │
│ - store refresh tokens hashed instead of in plain text │
│ - revoke the whole token family when a used token reappears │
└─────────────────────────────────────────────────────────────┘
Commit with this message? [y/N] y
committed 4f2a1c9 feat(auth): rotate refresh tokens on every use
- Go 1.21 or newer, to build it
- git
- The Claude Code CLI, already signed in
The message is generated by shelling out to claude --print, so lazycommit
reuses the credentials that CLI already holds. There is no API key to
configure and no extra subscription to pay for.
git clone <this repo>&&cd lazycommit
make install # builds and copies the binary to ~/.local/binOr, without the Makefile:
go build -o lazycommit .&& mv lazycommit ~/.local/bin/Make sure ~/.local/bin is on your PATH.
lazycommit # describe and commit what is already staged
lazycommit -a # stage everything first, then describe and commit
lazycommit --dry-run # print the message and stop, committing nothing
lazycommit -y # skip the confirmation prompt
lazycommit --hint "ticket ASAP-1421, part of the PCI audit"| Flag | Default | What it does |
|---|---|---|
-a, --all | off | Stage every change (git add -A) before describing it |
-y, --yes | off | Commit without asking for confirmation |
-n, --dry-run | off | Print the message and exit without committing |
--hint | empty | Extra context the diff cannot show: a ticket ID, the reason behind the change |
--model | sonnet | Model to generate the message with |
--lang | english | Language to write the message in |
--timeout | 2m | How long to wait for the model |
--max-diff | 120000 | Maximum diff size in bytes sent to the model |
Answering anything other than y or yes aborts, leaving the index exactly as
it was. Nothing is committed unless you say so.
When stdout is not a terminal the message is printed raw, without the frame:
lazycommit --dry-run > message.txt
lazycommit --dry-run | xclip -selection clipboardStatus lines and errors always go to stderr, so they never pollute the piped message.
git diff --stagedproduces the diff, andgit diff --staged --statthe file summary that survives even when a huge diff has to be truncated.- The diff, the current branch and the last 10 commit subjects (as a style reference) go to the model over stdin.
- The model runs with a replaced system prompt and every tool denied, so it
behaves as a plain diff-to-message transformer rather than an agent. It runs
from a scratch directory, which keeps the target repository's
CLAUDE.md, hooks and settings out of the session. - The message is framed on screen, and
git commit -F -records it only after you accept. Committing through git means your hooks, GPG signing andcommit.templateconfig all still apply.
The model is instructed to follow Conventional
Commits: a type(scope): subject line in
the imperative mood capped at 72 characters, a blank line, then a body wrapped
at 72 columns explaining what changed and why. It never adds Co-authored-by
or any other trailer.
make build # compile to ./bin/lazycommit
make test# run the tests
make check # gofmt, go vet and the testsmain.go entry point
cmd/root.go flags and the accept/commit flow
internal/git/git.go the git commands, shelled out
internal/ai/provider.go the Provider interface and the request sent to it
internal/ai/claudecli.go the claude CLI provider and its system prompt
internal/ui/ the frame, the spinner, the confirmation prompt
Adding another backend means implementing ai.Provider and selecting it in
cmd/root.go; nothing else knows which model wrote the message.