01
The problem with Git's messages
Git tells you exactly what happened, in Git's terms. “You have unmerged paths.” “HEAD detached at 3f2a1c9.” “Updates were rejected because the remote contains work that you do not have locally.” All true, and if you're new, none of it tells you what to type next.
So whygit does one job: look at the repository, say what's going on in plain English, and list the commands that get you out, each with a line on what it does. When a fix can lose work, like `git push --force` or `git reset --hard`, it says so and offers the safer option first.
- Operations stopped halfway: merge conflicts (with the files), a rebase, cherry-pick or revert in progress.
- Where you are: a detached HEAD (and whether you have commits on no branch), a branch that has diverged from GitHub, no upstream yet.
- Things that will hurt later: a CSV over GitHub's 100 MB limit about to be committed, a staged `.env` or key file, a repository accidentally created in your home folder, a project nested inside another one.
- Pasted errors: a rejected push, “refusing to merge unrelated histories”, password authentication no longer working on GitHub, “Please tell me who you are”, and more.
02
Two ways in, one engine
The same engine sits behind a command and an MCP server. On the command line, `whygit` diagnoses the folder you're in, and `whygit explain` takes an error you paste. Through MCP, a coding agent gets three tools: diagnose the repository, explain an error, and summarise its state.
The agent side was designed with the rules I'm testing in agent-interface-lab: few tools, a one-line description saying when to use each, typed inputs, small structured results, and errors that say how to recover. Each tool is also marked read-only and safe to repeat, so a client can treat it as harmless.
03
Read-only, and proven
A tool that tells people how to fix their repository must never touch it. That's easy to promise and easy to break: even `git status` can rewrite a file inside `.git` when it refreshes the index. So the promise is enforced in code and checked by tests.
There's exactly one place in the package that starts a process. It only runs Git subcommands on a short read-only list, rejects any options that write, and turns off the index refresh. It never fetches, so ahead/behind counts are reported “as of your last fetch”. Credentials in remote URLs are hidden from all output.
- 38 scenarios build the real state with the real `git` binary in a temporary folder: a genuine conflict, a genuine detached HEAD, a clone that has diverged from a local stand-in for GitHub.
- For each one, every file in the folder is fingerprinted (size, modification time, hash) before and after whygit runs, and the two must match.
- A control test shows the check can see a change: running plain `git status` on the same repository does rewrite its index.
- A code check confirms nothing else in the package can start a process, and a spy records every Git call made during a diagnosis.
04
When the test caught something
On the second CI run, the macOS job failed the read-only test. In one scenario, the modification time on the `.git/objects` folder was different after whygit ran. A broken promise would look exactly like that, so it needed explaining, not a retry.
The timing pointed elsewhere. The change landed about 50 ms after the scenario's last `git commit`. Git can run housekeeping in the background after a commit, so the folder could still have been changing when the test took its first fingerprint. I couldn't reproduce it locally in 15 runs of all 38 scenarios.
So the test now waits until the folder has stopped changing (two fingerprints 100 ms apart must match) before running whygit. If the folder is still moving on its own, the test says so instead of blaming whygit. The test repositories also turn off Git's background maintenance. After that, CI passed three runs in a row on every platform.
A broken promise would look exactly like that, so it needed explaining, not a retry.
05
Shipping it
CI runs the tests against the built package, not the source folder, on Python 3.10 to 3.14 on Linux and on macOS and Windows. The first Windows run failed twice, both in the tests rather than the tool: the fingerprint used Windows path separators, and Python quietly wrote the line-ending test data with Windows line endings, so there was nothing to warn about.
Releases publish themselves: pushing a version tag builds the package and uploads it to PyPI through trusted publishing, so no password or token is stored anywhere. The first name, gitwhy, was refused because PyPI treats it as the same name as an existing “git-why”, so it became whygit. It's also listed in the official MCP registry, so MCP clients can find it there.
Running 0.1.0 on my own projects folder turned up one more problem. Outside a repository, it lists the repositories just below to help you find the right one, but it cut the list at eight without saying so. 0.1.1 says “… and 5 more”.
| Use it | Command |
|---|---|
| Run once, no install | uvx whygit |
| Explain an error | whygit explain "<paste the error>" |
| Add to Claude Code | claude mcp add --scope user whygit -- uvx whygit mcp |