DEV Community

Cover image for git-mcp-server crashed my repository
Stefan Neidig
Stefan Neidig

Posted on • Edited on

git-mcp-server crashed my repository

Disclaimer: AI was used for checking grammar and to improve readability. Content was written by human.

Just a quick story that happened yesterday and perhaps a warning to the vibe coders out there. As the title suggests, I too tend to vibe code from time to time. When I do, I go all out and allow the agent to even commit its changes. I use a git-mcp-server for this (see https://github.com/modelcontextprotocol/servers/tree/main/src/git), which does a great job until I noticed some strange behaviour during one vibe coding session.

The situation

After the agent committed everything, there were still some staged files. Not knowing what they were, I ran git status myself and found the following situation

M ./.git/index
M ./.git/logs/HEAD
M ./.git/logs/reds/heads/feature/...
M ./.git/refs/heads/feature/....
# and other non git related files
Enter fullscreen mode Exit fullscreen mode

Part of my .git directory (in particular the index) was staged and marked as changed. However, this means that these files were checked into the VCS to begin with, which shouldn't happen. I tried to find the MCP tool call that was responsible for this. Here is the malicious action in question:

git_add
Repo path: "/Users/..../root/path/of/project"
Files: "."
Enter fullscreen mode Exit fullscreen mode
git_commit
Repo path: "/Users/..../root/path/of/project"
Message: "chore: some message"
Enter fullscreen mode Exit fullscreen mode

As I was in a true vibe coding session, I barely checked what the agent did. But even if I had, I would never have guessed that a “git add .” would lead to the corruption of my local repository (and, as I later found out, my remote repository as well).

The problem

As I searched for issues reporting the same behavior, I found this: https://github.com/modelcontextprotocol/servers/issues/2373

So basically, what was happening was that all files starting with a . were staged, and not just the current directory, which is usually denoted by .. I do not want to bash the maintainers of the MCP server, especially since this was fixed rather quickly (I’m still not sure why this happened to me yesterday). What I would like to do is show you how to fix it once you are in that situation, as the solution is not that straightforward. I also want to talk about some learnings I drew from this incident.

The solution

Git has a built-in behavior that prevents the .git directory from being put into VCS. Somehow, the MCP tool managed to bypass this safeguard. So let’s figure out how to fix it.

First of all, what’s the big deal about having this in VCS, you might ask? For starters, Git behaves unreliably from that point on. Commits no longer work properly — it doesn’t throw an error, but it also doesn’t commit anything. Secondly, you’re no longer able to push to the remote repository. Check out this log:

git push origin develop 

Enumerating objects: 14486, done.
Counting objects: 100% (14486/14486), done.
Delta compression using up to 12 threads
Compressing objects: 100% (12446/12446), done.
remote: error: object 53d0cf36e260779eb90f3a546842ac0a38327f48: hasDotgit: contains '.git'
remote: fatal: fsck error in packed object
error: remote unpack failed: index-pack failed
To github.com:.../....git
 ! [remote rejected] develop -> develop (failed)
Enter fullscreen mode Exit fullscreen mode

Git has a built-in tool that checks whether .git files have been checked into VCS and prevents the remote from accepting changes that include such commits. These are just two obvious breaking points. I would assume there are many more. We can even check if we have commits that include .git files.

git fsck --full | grep '.git'

Checking object directories: 100% (256/256), done.
warning in tree 783fc820c5d91ee46c64fc0d08d7413474451718: hasDot: contains '.'
warning in tree 53d0cf36e260779eb90f3a546842ac0a38327f48: hasDotgit: contains '.git'
warning in tree e0eabd8ea18232fa10b87de0be0548259b18f11f: hasDot: contains '.'
Checking objects: 100% (14492/14492), done.
Verifying commits in commit graph: 100% (15/15), done.
Enter fullscreen mode Exit fullscreen mode

So 53d0cf3 seems to be one of these commits. The unfortunate part is that we need to remove this from our repository, and the only way to do that is by rewriting the Git history. This is a huge issue, especially for large repositories with multiple PRs and team members working on them, as it essentially breaks everything. But as far as I know (please correct me if I’m wrong), this is our only option. So let’s get on with it.

git filter-repo --force --path-glob '.*' --invert-paths
Enter fullscreen mode Exit fullscreen mode

Here, we are scanning all of our commits for files starting with a .. I tried using the glob .git*, but that didn’t seem to work. --invert-paths means we remove all files that match the pattern, and --force tells Git to enforce the rewriting of the history, even if the repository is not a fresh clone. After running this, all . files are removed from every commit, and each commit receives a new hash.

Next, we should add and commit all necessary dotfiles again in a new commit. For me, this was only the .gitignore file, so the following worked:

git checkout HEAD@{1} -- .gitignore && git commit -m "chore: restore gitingore file" 
Enter fullscreen mode Exit fullscreen mode

Now we need to check for .git files in our commits again and shouldn't find anything

git fsck

Checking object directories: 100% (256/256), done.
Checking objects: 100% (182/182), done.
dangling blob a111f1ad14e068fe85b133634a4917cca01ee5b4
Verifying commits in commit graph: 100% (14/14), done.
Enter fullscreen mode Exit fullscreen mode

Looking good. We then need to add the remote again, since it is removed when running git filter-repo. We now essentially have a new repository that coincidentally shares the same history, but with different commit hashes. Therefore, we need to force push, which breaks PRs, issues, and anything tied to the previous remote history.

We also need to force merge into other branches (most likely master or main) and force push them as well. It’s not something I hope to do in the future again, but the alternative would have been to completely reinitialize Git and commit everything as an initial commit. For me, this approach was the better option.

The learnings

I was happy to have my Git repository back, but it was crucial to prevent this from happening ever again. Here are some things that might help:

  • Put **/.git into .gitignore. It might seem odd, but it would have saved me from a lot of trouble. Better safe than sorry.
  • Use Git hooks to check for .git files before committing. There are tools to help you get started with Git hooks (e.g., Husky, Lefthook).
  • Do not let the agent commit (i.e., avoid using a Git MCP Server). While this might seem obvious, I’m not entirely against using them. It’s like falling off a horse and never riding again — there will always be bugs, and we should focus on finding solutions rather than abandoning the software altogether.
  • Check the MCP Server before using it. I will be more cautious in the future about which MCP server I use. I probably would have never anticipated this bug, let alone read the code to try and understand what the server is doing. But sourcing MCP servers from reliable projects — and checking issues, stars, and popularity — can certainly help.
  • Really understand the crucial parts of your automation. Don’t blindly trust it. Mishaps, bugs, and catastrophes can happen. A broken Git history might be one of the worst-case scenarios. When automating parts of your core infrastructure, make sure you understand what is happening and monitor it closely. If in doubt, avoid it (but only if you’re really in doubt).

Conclusion

My doctor once told me (regarding my health), "Not as bad as it could have been." That pretty much sums up this story in one sentence. While I’m not happy about having to completely rewrite the repository’s history, I’m certainly glad that I managed to get it back to a functional state. I learned a few things about Git (again) and now have a story to tell. But boy, I don’t ever want to deal with this again.

Have you every had an issue like that? Is there a better way to deal with this issue? Please let me know.

Top comments (0)