Uh oh!
There was an error while loading. Please reload this page.
fix(repos): give create_or_update_file callers a SHA they can actually get - #3131
Open
dylanpulver wants to merge 1 commit into
Open
fix(repos): give create_or_update_file callers a SHA they can actually get#3131dylanpulver wants to merge 1 commit into
dylanpulver wants to merge 1 commit into
Conversation
…y get The create_or_update_file tool description and both of its SHA errors told the caller to run `git rev-parse <branch>:<path>`. The caller is an MCP client talking to the GitHub API, and the same description tells it not to use this tool for local file operations, so it has no working tree to run that command against. Point the description at get_file_contents instead, which returns the blob SHA over the API. In the already-exists error the server has just fetched the file, so return that SHA directly rather than asking for a round trip. The stale-SHA error already interpolates the current SHA, so it only needed the impossible instruction removed.
Collaborator
Hi, so the reason this protection exists is so that the model is updating a version of a file that it is expecting to update. Race conditions occur where two users update a file at same time and second one undoes the work. Just returning the head sha of the file is risky. I'd it wasn't we'd just update the file and not error. Get file content returns the SHA so the model can use it. I'm ok with an error that doesn't assume git access, but the model needs to know what version of a file it's actually editing and refetch it if it's changed since. FWIW I see you do cover this, and I will hopefully look fully soon. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
create_or_update_filetold its caller to rungit rev-parse <branch>:<path>to get a blob SHA, which an MCP client has no way to do. This replaces that instruction with the SHA itself where the server has it, and with a pointer toget_file_contentswhere it does not.Why
Fixes#3130
The tool description says: "Use this tool to create or update a file in a GitHub repository remotely; do not use it for local file operations." Two lines later it says:
git rev-parse <branch>:<path>reads a local object database. A client that is using this tool over the API has no clone and no working tree, so it cannot run that. The same instruction appears in both SHA error paths, so an agent that hits either one is handed a recovery step it cannot perform, and it gets stuck in the loop described in #3130.The already-exists path is the sharper case. Before returning that error the handler calls
client.Repositories.GetContentsand holds the result inexistingFile. The SHA the caller is being sent away to fetch is in scope at that point.What changed
get_file_contents, which returns the blob SHA over the API, instead ofgit rev-parse.existingFile.GetSHA()so the caller can retry immediately with no extra round trip.git rev-parsesentence. It interpolates the current SHA, so nothing else was needed there.pkg/github/__toolsnaps__/create_or_update_file.snapfor the description change.Test_CreateOrUpdateFile, plus an assertion in the shared error branch that no error path in this tool emitsgit rev-parse.Error strings before:
After:
I grepped the whole package for other local-git instructions in tool-facing strings. The three sites fixed here were the only hits outside
Dockerfileandscript/.Relationship to #2772
#2772 rewrites
create_or_update_fileto commit through the GraphQLcreateCommitOnBranchmutation. It keeps the SHA validation block, and it does not modify the description or either error string, so the two changes touch different lines of the same function.I checked this rather than assumed it. Cherry-picking this commit onto
pull/2772/headappliespkg/github/repositories.goand the toolsnap with no conflict.pkg/github/repositories_test.goconflicts in two table entries, but only because #2772 branched from a Junemainthat predates theexpectedErrMsgsfield and theerrorTextbuilder in the shared assertion block. #2772 currently reports as conflicting withmainand will need that same rebase regardless of this PR.Landing this separately seems worth it because the bad instruction is shipping today while #2772 is a large architectural change with an uncertain timeline. The fix here is small enough to review on its own. If #2772 lands first I am happy to rebase this on top of it.
MCP impact
The
create_or_update_filedescription changed, and its two SHA error messages changed. Inputs, outputs, and success behavior are untouched. The already-exists error now carries a blob SHA, which the caller had read access to anyway, since the server obtained it with the caller's own credentials on the request that produced the error.Prompts tested (tool changes only)
Read against the mocked handler paths in
Test_CreateOrUpdateFilerather than a live token, since I do not have an e2e PAT for this repo. The two error paths correspond to these agent-facing cases:docs/example.mdonmainto say X" where the file exists and the agent does not supply asha. Previously returned thegit rev-parseinstruction; now returns the current blob SHA to retry with.docs/example.mdonmain" with ashacaptured before someone else pushed to the branch. Previously returned thegit rev-parseinstruction alongside the current SHA; now returns the current SHA and a retry instruction.Security / limits
The already-exists error surfaces a blob SHA the server just read using the caller's own credentials on that same call. A caller that could not read the file would have received the
GetContentserror instead.Tool renaming
deprecated_tool_aliases.goLint & tests
./script/lint./script/test./script/lintreports0 issues../script/testpasses across all packages.go vet ./...is clean. Both were run on a cleanmaincheckout first for a baseline, and both were green there too.I also confirmed the new assertions fail against unmodified
repositories.go, so they are testing the fix rather than passing vacuously.Not run: the
e2e/suite, which needsGITHUB_MCP_SERVER_E2E_TOKEN.Docs
script/generate-docsproduces no diff. README lists the tool title and parameters, not the description body.