I wrote git meh as a Bash script on a Sunday afternoon. It was a joke, a tool for the terminally lazy that stages everything, asks an AI to write a commit message, and pushes without questioning. I threw in randomized status messages mocking the user’s lack of professional standards, put a warning on the README that said this was absolutely not for professional use, and called it done.
Then other people and I started using it. A few folks told me they found it handy and I started using it for more serious tasks. It was not a joke, but a tool that was useful for serious coding tasks.

Why rewrite in Go?
I’ve been writing PHP and TS/JS for years. I know Bash well enough to be dangerous. But in Go, I was a complete beginner. PHP and TS/JS are mostly useless for building a shell script and bash is hideous, so I decided to venture into the wild world of Go for a more serious rewrite of Git meh.
The Bash version was about 300 lines of script. It worked, but it had sharp edges:
- No proper error handling (one failed curl and you were staring at raw HTML)
- No retry logic (if the API burped, you lost your message)
- No graceful Ctrl+C handling (good luck if you fat-fingered the terminal)
- No inline editing (you could say “edit” but then you had to type from scratch)
- No cross-platform builds (hope you like having Bash installed)
- No tests (lol)
Go fixed all of that, and I learned a new programming language along the way.
What changed
The Go version is structured into packages, aiapi for API communication, config for environment parsing, git for git command wrappers. There’s a version package, a Makefile, integration tests, linting via golangci-lint, and security scanning with govulncheck.
Some features of the new code base:
Retry logic with exponential backoff. If the primary model fails (timeout, 5xx, context-length exceeded), gitmeh retries up to 3 times with 1s/2s/4s backoff, then tries each fallback model in order. A 401 skips retries immediately — no point hammering a locked door.
Diff truncation that makes sense. The old Bash version just took the first 800 bytes per file. The Go version parses the unified diff into sections, keeps all file headers (they’re small), and proportionally allocates the remaining byte budget across files by hunk size. Truncated hunks get a # hunk truncated marker so you know what happened.
Interactive inline editing. Instead of dumping you back to a blank prompt, you can edit the AI-suggested message right there in the terminal with cursor keys and working backspace. It uses raw terminal mode, handles multi-byte UTF-8 properly, and even redraws the cursor position when the line wraps. This was fiddly to get right; things which are trivial in Bash become more complex in a language not native to the terminal.
Graceful Ctrl+C. Cancels the HTTP context immediately, cleans up the terminal, and exits without leaving your terminal in raw mode.
Cross-compilation. One make cross and you get binaries for Linux and macOS, amd64 and arm64. The install script selects the right one automatically.
Dev/prod build separation. Developer builds target ai.hellyer.test with self-signed TLS; release builds target ai.hellyer.kiwi with full verification. Controlled by a single linker flag.
The free API
The default endpoint is https://ai.hellyer.kiwi/v1, a Laravel-based API I run myself. It’s free, no account required, no signup, no API key needed. You just run git meh and it works. Note: This is only useful for getting commit messages, nothing else.
At the time of writing, the backend is running Deepseek V4 Flash, which produces good commit messages. If costs get too high I may need to drop to a smaller model, but for now the quality/price ratio is excellent and I’m assuming if this app becomes popular it’ll take a while and the cost of these services will decrease anyway.
If you’d rather use your own provider, set GITMEH_API_BASE, GITMEH_API_KEY, and optionally GITMEH_MODEL; any OpenAI-compatible endpoint will do (OpenRouter, OpenAI itself, whatever). The code treats the prompt as a system message and appends the diff as a user message, so the model actually has context.
Did it get more useful?
Yes. I was wrong in my original README when I said this wasn’t suitable for professional use. I’m still not recommending you run it blindly on a team repo without reviewing what’s staged, that part is dangerous, but the workflow of “stage, AI-draft a message, review, edit if needed, commit” is faster than writing messages by hand and no worse in quality; in most cases, I’d guess it’s much higher in quality.
I now use it on my own projects regularly. The confirmation prompt (Y/n/e) lets me review the message before committing. The inline edit lets me tweak it when the AI gets the tone wrong. The whole thing takes about 10 seconds.
How to install
The Go rewrite is now live, with instructions hosted on the official Git meh page.
What I learned
Go is an oddball language. I could feel the same OOP concepts coming through, but without classes or “normal looking” methods. In principle, it seems like a good language, but the change in how it handles OOP-like architecture is confusing if, like me, you’re used to traditional classes and methods way of handling things.
As expected, it made some stuff which is very difficult in Bash become significantly easier. What I wasn’t expecting was that it would make some very basic things in Bash significantly more complicated (editing a text prompt suddenly became a lot more complicated than I anticipated).
Future of this project
I hope some of you find this useful. I certainly am. Since I’m using it regularly, I will add more complexity to it over time. For example, during testing I kept finding myself wanting to only get commit messages for a couple of files, while leaving the rest un-committed; so this will become a feature in the coming months as it bugs me each time I need to work around it. If you can think of any other features you would like, just let me know.