From b700da3eb39c94aefe74c204c2ad8f66070796d6 Mon Sep 17 00:00:00 2001 From: mudler-agent Date: Tue, 29 Sep 2026 10:37:31 +0200 Subject: [PATCH] fix(huggingface): list repos nested more than one directory deep (#12355) The HuggingFace tree API returns each entry's path relative to the repo root ("assets/plots", not "plots"). The recursive listing prefixed the parent directory again, so it requested "assets/assets/plots", got a 404, and failed the whole listing. The importer then treated the URI as a non-HF repo and no importer matched. This broke the import of GGUF repos that keep per-quant subfolders next to a nested assets tree, such as ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF. The test mock now returns root-relative directory paths like the real API and routes on the exact tree path, so a doubled path 404s. Assisted-by: Claude:claude-opus-5-5 [Claude Code] Signed-off-by: Ettore Di Giacinto Co-authored-by: Ettore Di Giacinto --- pkg/huggingface-api/client.go | 16 +++++----------- pkg/huggingface-api/client_test.go | 20 ++++++++++++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pkg/huggingface-api/client.go b/pkg/huggingface-api/client.go index 1d1c7ae3c..524fe57c5 100644 --- a/pkg/huggingface-api/client.go +++ b/pkg/huggingface-api/client.go @@ -304,14 +304,12 @@ func (c *Client) listFilesInPath(repoID, path string) ([]FileInfo, error) { switch item.Type { // If it's a directory/folder, recursively list its contents case "directory", "folder": - // Build the subfolder path + // The tree API returns every entry's path relative to the repo + // root ("assets/plots", not "plots"), so it is already the path + // to recurse into. Prefixing the parent again requested + // "assets/assets/plots", which 404s and failed the whole listing + // for any repo nested more than one directory deep. subPath := item.Path - if path != "" { - subPath = fmt.Sprintf("%s/%s", path, item.Path) - } - - // Recursively get files from subfolder - // The recursive call will already prepend the subPath to each file's path subFiles, err := c.listFilesInPath(repoID, subPath) if err != nil { return nil, fmt.Errorf("failed to list files in subfolder %s: %w", subPath, err) @@ -319,10 +317,6 @@ func (c *Client) listFilesInPath(repoID, path string) ([]FileInfo, error) { allFiles = append(allFiles, subFiles...) case "file": - // It's a file, prepend the current path to make it relative to root - // if path != "" { - // item.Path = fmt.Sprintf("%s/%s", path, item.Path) - // } allFiles = append(allFiles, item) } } diff --git a/pkg/huggingface-api/client_test.go b/pkg/huggingface-api/client_test.go index feac4dba3..6f60d3ef8 100644 --- a/pkg/huggingface-api/client_test.go +++ b/pkg/huggingface-api/client_test.go @@ -459,7 +459,7 @@ var _ = Describe("HuggingFace API Client", func() { }, { "type": "directory", - "path": "nested", + "path": "subfolder/nested", "size": 0, "oid": "nesteddir123" } @@ -483,15 +483,23 @@ var _ = Describe("HuggingFace API Client", func() { server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { urlPath := r.URL.Path w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if strings.Contains(urlPath, "/tree/main/subfolder/nested") { + // Route on the exact tree path: the real API 404s on a wrong + // path, and nested entries carry root-relative paths + // ("subfolder/nested"), so a prefix match would hide a + // doubled "subfolder/subfolder/nested" request. + _, treePath, _ := strings.Cut(urlPath, "/tree/main") + switch treePath { + case "/subfolder/nested": + w.WriteHeader(http.StatusOK) w.Write([]byte(mockNestedResponse)) - } else if strings.Contains(urlPath, "/tree/main/subfolder") { + case "/subfolder": + w.WriteHeader(http.StatusOK) w.Write([]byte(mockSubfolderResponse)) - } else if strings.Contains(urlPath, "/tree/main") { + case "": + w.WriteHeader(http.StatusOK) w.Write([]byte(mockRootResponse)) - } else { + default: w.WriteHeader(http.StatusNotFound) } }))