From 9f9b93aa6aa1c5d3aedb2d22533ee61d68bf7c83 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 15 Apr 2026 23:34:54 +0200 Subject: [PATCH] URL-encode parentPath in skills discovery API call The parentPath parameter in the contents API path was not URL-encoded, which would cause failures when paths contain spaces or other special characters. Apply url.PathEscape() to parentPath, consistent with the rest of the file. commitSHA is left unescaped since SHAs are hex-only and never need encoding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/skills/discovery/discovery.go | 2 +- internal/skills/discovery/discovery_test.go | 27 ++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/internal/skills/discovery/discovery.go b/internal/skills/discovery/discovery.go index 84f2aa596..4a6bdb940 100644 --- a/internal/skills/discovery/discovery.go +++ b/internal/skills/discovery/discovery.go @@ -491,7 +491,7 @@ func DiscoverSkillByPath(client *api.Client, host, owner, repo, commitSHA, skill } parentPath := path.Dir(skillPath) - apiPath := fmt.Sprintf("repos/%s/%s/contents/%s?ref=%s", url.PathEscape(owner), url.PathEscape(repo), parentPath, commitSHA) + apiPath := fmt.Sprintf("repos/%s/%s/contents/%s?ref=%s", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(parentPath), commitSHA) var contents []struct { Name string `json:"name"` diff --git a/internal/skills/discovery/discovery_test.go b/internal/skills/discovery/discovery_test.go index 2de7ef683..e2333dfd1 100644 --- a/internal/skills/discovery/discovery_test.go +++ b/internal/skills/discovery/discovery_test.go @@ -699,7 +699,7 @@ func TestDiscoverSkillByPath(t *testing.T) { skillPath: "skills/monalisa/issue-triage", stubs: func(reg *httpmock.Registry) { reg.Register( - httpmock.REST("GET", "repos/monalisa/octocat-skills/contents/skills/monalisa"), + httpmock.REST("GET", "repos/monalisa/octocat-skills/contents/skills%2Fmonalisa"), httpmock.JSONResponse([]map[string]interface{}{ {"name": "issue-triage", "path": "skills/monalisa/issue-triage", "sha": "tree-sha", "type": "dir"}, })) @@ -720,6 +720,31 @@ func TestDiscoverSkillByPath(t *testing.T) { wantName: "issue-triage", wantNS: "monalisa", }, + { + name: "parent path with spaces is URL encoded", + skillPath: "my skills/code-review", + stubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("GET", "repos/monalisa/octocat-skills/contents/my%20skills"), + httpmock.JSONResponse([]map[string]interface{}{ + {"name": "code-review", "path": "my skills/code-review", "sha": "tree-sha", "type": "dir"}, + })) + reg.Register( + httpmock.REST("GET", "repos/monalisa/octocat-skills/git/trees/tree-sha"), + httpmock.JSONResponse(map[string]interface{}{ + "sha": "tree-sha", "truncated": false, + "tree": []map[string]interface{}{ + {"path": "SKILL.md", "type": "blob", "sha": "blob-sha"}, + }, + })) + reg.Register( + httpmock.REST("GET", "repos/monalisa/octocat-skills/git/blobs/blob-sha"), + httpmock.JSONResponse(map[string]interface{}{ + "sha": "blob-sha", "encoding": "base64", "content": "IyBTa2lsbA==", + })) + }, + wantName: "code-review", + }, { name: "strips trailing SKILL.md from path", skillPath: "skills/code-review/SKILL.md",