Skip to content

Commit c79439e

Browse files
fix: handle empty files in get_file_contents (#2042)
1. Empty (0-byte) files caused an unhandled error because the GitHub API returns null content with base64 encoding for them; GetContent() fails with "malformed response: base64 encoding of null content". Return empty text/plain content directly, bypassing decoding entirely. Co-authored-by: Ksenia Bobrova <almaleksia@github.com>
1 parent ccb9b53 commit c79439e

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

pkg/github/repositories.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,20 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool
726726
successNote = fmt.Sprintf(" Note: the provided ref '%s' does not exist, default branch '%s' was used instead.", originalRef, rawOpts.Ref)
727727
}
728728

729+
// Empty files (0 bytes) have no content to decode; return
730+
// them directly as empty text to avoid errors from
731+
// GetContent when the API returns null content with a
732+
// base64 encoding field, and to avoid DetectContentType
733+
// misclassifying them as binary.
734+
if fileSize == 0 {
735+
result := &mcp.ResourceContents{
736+
URI: resourceURI,
737+
Text: "",
738+
MIMEType: "text/plain",
739+
}
740+
return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded empty file (SHA: %s)%s", fileSHA, successNote), result), nil, nil
741+
}
742+
729743
// For files >= 1MB, return a ResourceLink instead of content
730744
const maxContentSize = 1024 * 1024 // 1MB
731745
if fileSize >= maxContentSize {

pkg/github/repositories_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,40 @@ func Test_GetFileContents(t *testing.T) {
351351
Title: "File: large-file.bin",
352352
},
353353
},
354+
{
355+
name: "successful empty file content fetch",
356+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
357+
GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
358+
GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
359+
GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
360+
w.WriteHeader(http.StatusOK)
361+
fileContent := &github.RepositoryContent{
362+
Name: github.Ptr(".gitkeep"),
363+
Path: github.Ptr(".gitkeep"),
364+
SHA: github.Ptr("empty123"),
365+
Type: github.Ptr("file"),
366+
Content: nil,
367+
Size: github.Ptr(0),
368+
Encoding: github.Ptr("base64"),
369+
}
370+
contentBytes, _ := json.Marshal(fileContent)
371+
_, _ = w.Write(contentBytes)
372+
},
373+
}),
374+
requestArgs: map[string]any{
375+
"owner": "owner",
376+
"repo": "repo",
377+
"path": ".gitkeep",
378+
"ref": "refs/heads/main",
379+
},
380+
expectError: false,
381+
expectedResult: mcp.ResourceContents{
382+
URI: "repo://owner/repo/refs/heads/main/contents/.gitkeep",
383+
Text: "",
384+
MIMEType: "text/plain",
385+
},
386+
expectedMsg: "successfully downloaded empty file",
387+
},
354388
{
355389
name: "content fetch fails",
356390
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{

0 commit comments

Comments
 (0)