git_checkout reports "Switched to branch 'X'" when the checkout actually detached HEAD
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 86/100
Research direction
Start in src/git/src/mcp_server_git/server.py around lines 202-204 and inspect the existing git_checkout tests in src/git/tests/test_server.py. Verify branch checkouts retain the existing message while revisions that detach HEAD report the detached state, then run the test file and confirm the reported behavior matches the checkout outcome.
Written by the indexing model from the issue text.
Description
What happens
git_checkout validates branch_name with repo.rev_parse(...), which resolves any revision — a commit sha, a tag, HEAD~1, a remote-tracking ref — not only branch names. It then checks out and returns a fixed string:
repo.rev_parse(branch_name) # Validates branch_name is a real git ref, throws BadName if not
repo.git.checkout(branch_name)
return f"Switched to branch '{branch_name}'"
src/git/src/mcp_server_git/server.py lines 202-204 on main (the flag-injection guard above them is elided). The tool description is Switches branches, the parameter is branch_name, and the README says the same: "git_checkout … Returns: Confirmation of branch switch". For every revision that is not a branch, the checkout detaches HEAD and the reply still claims a branch switch.
Reproduction
mcp-server-git 2026.8.18, mcp 1.30.0, over stdio, on a fresh repo with three commits, a tag v1 and a branch feature:
branch_name "feature" -> Switched to branch 'feature' | On branch feature (correct)
branch_name "nonexistent" -> isError=true, repo unchanged (correct)
branch_name "49cea63" (a sha) -> Switched to branch '49cea63' | HEAD detached at 49cea63
branch_name "v1" (a tag) -> Switched to branch 'v1' | HEAD detached at v1
branch_name "HEAD~1" -> Switched to branch 'HEAD~1' | HEAD detached at f1de938
branch_name "origin/main" -> Switched to branch 'origin/main' | HEAD detached at origin/main
branch_name "refs/heads/feature" -> Switched to branch 'refs/heads/feature' | HEAD detached at refs/heads/feature
The last two are the realistic ones: an agent writes origin/main or a full ref name far more often than a raw sha.
Why this matters for the calling model
The reply is the only thing the model sees: content is a single TextContent, structuredContent is null, isError is false, the tool declares no outputSchema, and its annotations are static. Nothing in the response distinguishes an attached checkout from a detached one.
Git itself does distinguish them. git checkout <sha> writes the You are in 'detached HEAD' state advisory to stderr, and leaving a detached HEAD prints Warning: you are leaving 1 commit behind together with the git branch <new-branch-name> <sha> recipe. repo.git.checkout() returns '' and that advisory is discarded, so the server replaces git's own warning with a success sentence. Work committed from there is on no branch and reachable only through the reflog until gc expires it; the tool never says so, and nothing in its reply suggests calling git_status.
Suggested fix
Report the state rather than assume it:
repo.git.checkout(branch_name)
if repo.head.is_detached:
return f"HEAD is now detached at {repo.head.commit.hexsha[:7]}"
return f"Switched to branch '{repo.active_branch.name}'"
Verified against main: src/git/tests/test_server.py stays at 47 passed, and git_checkout on a branch name still returns Switched to branch '<name>'. Rejecting non-branch revisions would also close it, but that removes a capability some callers use deliberately.
Same root cause as #4762 and #4763 — no verification of the outcome after the git call — so this could fold into the same fix if that is easier to review.
- Dominant language
- TypeScript
- Stars
- 90.5k
- Forks
- 11.7k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 5
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from modelcontextprotocol/servers
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
modelcontextprotocol/servers#4830 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
modelcontextprotocol/servers#4829 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
modelcontextprotocol/servers#4812 ·
-
README gate confirmation command can be used by any commenter, bypassing the "readme: pending" gate Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
modelcontextprotocol/servers#4796 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
modelcontextprotocol/servers#4792 ·
All issues in modelcontextprotocol/servers
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
copse-dev/agent-pane#2953 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug clawsweeper:linked-pr-open clawsweeper:needs-live-repro clawsweeper:no-new-fix-pr impact:message-loss issue-rating: 🐚 platinum hermit P2 regression
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·