From a4ab187106918172f3782b751ce7d9396d024301 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Tue, 26 May 2026 10:40:57 +0100 Subject: [PATCH 1/4] support custom CLAUDE_CONFIG_DIR in install --- internal/skills/registry/registry.go | 8 ++++++ internal/skills/registry/registry_test.go | 19 +++++++++++++ pkg/cmd/skills/install/install_test.go | 34 +++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/internal/skills/registry/registry.go b/internal/skills/registry/registry.go index a5e018176cf..ae98570fe56 100644 --- a/internal/skills/registry/registry.go +++ b/internal/skills/registry/registry.go @@ -2,6 +2,7 @@ package registry import ( "fmt" + "os" "path/filepath" "strings" @@ -30,6 +31,8 @@ const ( DefaultAgentID = "github-copilot" + claudeConfigDirEnv = "CLAUDE_CONFIG_DIR" + sharedProjectSkillsDir = ".agents/skills" ) @@ -387,6 +390,11 @@ func (h *AgentHost) InstallDir(scope Scope, gitRoot, homeDir string) (string, er } return filepath.Join(gitRoot, h.ProjectDir), nil case ScopeUser: + if h.ID == "claude-code" { + if configDir := os.Getenv(claudeConfigDirEnv); configDir != "" { + return filepath.Join(configDir, "skills"), nil + } + } if homeDir == "" { return "", fmt.Errorf("could not determine home directory") } diff --git a/internal/skills/registry/registry_test.go b/internal/skills/registry/registry_test.go index bd0c4470963..a1beb89590c 100644 --- a/internal/skills/registry/registry_test.go +++ b/internal/skills/registry/registry_test.go @@ -71,6 +71,14 @@ func TestInstallDir(t *testing.T) { homeDir: "/home/monalisa", wantDir: filepath.Join("/tmp/monalisa-repo", ".claude", "skills"), }, + { + name: "claude code user scope", + hostID: "claude-code", + scope: ScopeUser, + gitRoot: "/tmp/monalisa-repo", + homeDir: "/home/monalisa", + wantDir: filepath.Join("/home/monalisa", ".claude", "skills"), + }, { name: "cursor project scope", hostID: "cursor", @@ -144,6 +152,17 @@ func TestInstallDir(t *testing.T) { } } +func TestInstallDir_ClaudeConfigDir(t *testing.T) { + t.Setenv(claudeConfigDirEnv, filepath.Join("/home", "monalisa", ".config", "claude")) + + host, err := FindByID("claude-code") + require.NoError(t, err) + + dir, err := host.InstallDir(ScopeUser, "/tmp/monalisa-repo", "/home/monalisa") + require.NoError(t, err) + assert.Equal(t, filepath.Join("/home", "monalisa", ".config", "claude", "skills"), dir) +} + func TestRepoNameFromRemote(t *testing.T) { tests := []struct { remote string diff --git a/pkg/cmd/skills/install/install_test.go b/pkg/cmd/skills/install/install_test.go index b126f1ac788..cd7baee9e95 100644 --- a/pkg/cmd/skills/install/install_test.go +++ b/pkg/cmd/skills/install/install_test.go @@ -1537,6 +1537,40 @@ func TestInstallProgress(t *testing.T) { assert.NotNil(t, installProgress(ios, 2)) } +func TestInstallRun_ClaudeCodeUserScopeUsesConfigDir(t *testing.T) { + homeDir := t.TempDir() + claudeConfigDir := t.TempDir() + t.Setenv("HOME", homeDir) + t.Setenv("USERPROFILE", homeDir) + t.Setenv("CLAUDE_CONFIG_DIR", claudeConfigDir) + + reg := &httpmock.Registry{} + defer reg.Verify(t) + + stubResolveVersion(reg, "monalisa", "skills-repo", "v1.0.0", "abc123") + stubDiscoverTree(reg, "monalisa", "skills-repo", "abc123", + singleSkillTreeJSON("git-commit", "treeSHA", "blobSHA")) + stubInstallFiles(reg, "monalisa", "skills-repo", "treeSHA", "blobSHA", gitCommitContent) + + ios, _, stdout, _ := iostreams.Test() + + err := installRun(&InstallOptions{ + IO: ios, + HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }, + GitClient: &git.Client{RepoDir: t.TempDir()}, + SkillSource: "monalisa/skills-repo", + SkillName: "git-commit", + Agent: "claude-code", + Scope: "user", + ScopeChanged: true, + Telemetry: &telemetry.NoOpService{}, + }) + require.NoError(t, err) + assert.Contains(t, stdout.String(), "Installed git-commit") + assert.FileExists(t, filepath.Join(claudeConfigDir, "skills", "git-commit", "SKILL.md")) + assert.NoFileExists(t, filepath.Join(homeDir, ".claude", "skills", "git-commit", "SKILL.md")) +} + func TestInstallRun_DeduplicatesSharedProjectDirAcrossHosts(t *testing.T) { homeDir := t.TempDir() t.Setenv("HOME", homeDir) From 7a89b91fb73566e0d2338eda2a67dc27e72905a3 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Tue, 26 May 2026 15:06:13 +0100 Subject: [PATCH 2/4] fix test --- internal/skills/registry/registry_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/skills/registry/registry_test.go b/internal/skills/registry/registry_test.go index a1beb89590c..f7d5aff913b 100644 --- a/internal/skills/registry/registry_test.go +++ b/internal/skills/registry/registry_test.go @@ -38,6 +38,8 @@ func TestFindByID(t *testing.T) { } func TestInstallDir(t *testing.T) { + t.Setenv(claudeConfigDirEnv, "") + tests := []struct { name string hostID string From 4343e405894bb1ee175cbd687e95d384dcda60fe Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 11 Jun 2026 16:29:03 +0100 Subject: [PATCH 3/4] test(skill): merge isolated test into table Signed-off-by: Babak K. Shandiz --- internal/skills/registry/registry_test.go | 27 +++++---- pkg/cmd/skills/install/install_test.go | 69 ++++++++++++----------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/internal/skills/registry/registry_test.go b/internal/skills/registry/registry_test.go index f7d5aff913b..6bb646023ce 100644 --- a/internal/skills/registry/registry_test.go +++ b/internal/skills/registry/registry_test.go @@ -42,6 +42,7 @@ func TestInstallDir(t *testing.T) { tests := []struct { name string + setup func(*testing.T) hostID string scope Scope gitRoot string @@ -81,6 +82,17 @@ func TestInstallDir(t *testing.T) { homeDir: "/home/monalisa", wantDir: filepath.Join("/home/monalisa", ".claude", "skills"), }, + { + name: "claude code user scope", + setup: func(t *testing.T) { + t.Setenv("CLAUDE_CONFIG_DIR", filepath.Join("/home", "monalisa", ".config", "claude")) + }, + hostID: "claude-code", + scope: ScopeUser, + gitRoot: "/tmp/monalisa-repo", + homeDir: "/home/monalisa", + wantDir: filepath.Join("/home", "monalisa", ".config", "claude", "skills"), + }, { name: "cursor project scope", hostID: "cursor", @@ -140,6 +152,10 @@ func TestInstallDir(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup(t) + } + host, err := FindByID(tt.hostID) require.NoError(t, err) @@ -154,17 +170,6 @@ func TestInstallDir(t *testing.T) { } } -func TestInstallDir_ClaudeConfigDir(t *testing.T) { - t.Setenv(claudeConfigDirEnv, filepath.Join("/home", "monalisa", ".config", "claude")) - - host, err := FindByID("claude-code") - require.NoError(t, err) - - dir, err := host.InstallDir(ScopeUser, "/tmp/monalisa-repo", "/home/monalisa") - require.NoError(t, err) - assert.Equal(t, filepath.Join("/home", "monalisa", ".config", "claude", "skills"), dir) -} - func TestRepoNameFromRemote(t *testing.T) { tests := []struct { remote string diff --git a/pkg/cmd/skills/install/install_test.go b/pkg/cmd/skills/install/install_test.go index cd7baee9e95..2239f66b4bb 100644 --- a/pkg/cmd/skills/install/install_test.go +++ b/pkg/cmd/skills/install/install_test.go @@ -292,6 +292,7 @@ func TestInstallRun(t *testing.T) { wantErr string wantStdout string wantStderr string + assert func(t *testing.T) }{ { name: "non-interactive without repo errors", @@ -1480,6 +1481,37 @@ func TestInstallRun(t *testing.T) { wantStdout: "Installed hidden-skill", wantStderr: "Skills in hidden directories", }, + { + name: "respect claude code config dir env var for user scope", + setup: func(t *testing.T) { + t.Setenv("CLAUDE_CONFIG_DIR", t.TempDir()) + }, + stubs: func(reg *httpmock.Registry) { + stubResolveVersion(reg, "monalisa", "skills-repo", "v1.0.0", "abc123") + stubDiscoverTree(reg, "monalisa", "skills-repo", "abc123", + singleSkillTreeJSON("git-commit", "treeSHA", "blobSHA")) + stubInstallFiles(reg, "monalisa", "skills-repo", "treeSHA", "blobSHA", gitCommitContent) + }, + opts: func(ios *iostreams.IOStreams, reg *httpmock.Registry) *InstallOptions { + t.Helper() + return &InstallOptions{ + IO: ios, + HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }, + GitClient: &git.Client{RepoDir: t.TempDir()}, + SkillSource: "monalisa/skills-repo", + SkillName: "git-commit", + Agent: "claude-code", + Scope: "user", + ScopeChanged: true, + Telemetry: &telemetry.NoOpService{}, + } + }, + assert: func(t *testing.T) { + assert.FileExists(t, filepath.Join(os.Getenv("CLAUDE_CONFIG_DIR"), "skills", "git-commit", "SKILL.md")) + assert.NoFileExists(t, filepath.Join(os.Getenv("HOME"), ".claude", "skills", "git-commit", "SKILL.md")) + }, + wantStdout: "Installed git-commit", + }, } for _, tt := range tests { @@ -1525,6 +1557,9 @@ func TestInstallRun(t *testing.T) { if tt.verify != nil { tt.verify(t) } + if tt.assert != nil { + tt.assert(t) + } }) } } @@ -1537,40 +1572,6 @@ func TestInstallProgress(t *testing.T) { assert.NotNil(t, installProgress(ios, 2)) } -func TestInstallRun_ClaudeCodeUserScopeUsesConfigDir(t *testing.T) { - homeDir := t.TempDir() - claudeConfigDir := t.TempDir() - t.Setenv("HOME", homeDir) - t.Setenv("USERPROFILE", homeDir) - t.Setenv("CLAUDE_CONFIG_DIR", claudeConfigDir) - - reg := &httpmock.Registry{} - defer reg.Verify(t) - - stubResolveVersion(reg, "monalisa", "skills-repo", "v1.0.0", "abc123") - stubDiscoverTree(reg, "monalisa", "skills-repo", "abc123", - singleSkillTreeJSON("git-commit", "treeSHA", "blobSHA")) - stubInstallFiles(reg, "monalisa", "skills-repo", "treeSHA", "blobSHA", gitCommitContent) - - ios, _, stdout, _ := iostreams.Test() - - err := installRun(&InstallOptions{ - IO: ios, - HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil }, - GitClient: &git.Client{RepoDir: t.TempDir()}, - SkillSource: "monalisa/skills-repo", - SkillName: "git-commit", - Agent: "claude-code", - Scope: "user", - ScopeChanged: true, - Telemetry: &telemetry.NoOpService{}, - }) - require.NoError(t, err) - assert.Contains(t, stdout.String(), "Installed git-commit") - assert.FileExists(t, filepath.Join(claudeConfigDir, "skills", "git-commit", "SKILL.md")) - assert.NoFileExists(t, filepath.Join(homeDir, ".claude", "skills", "git-commit", "SKILL.md")) -} - func TestInstallRun_DeduplicatesSharedProjectDirAcrossHosts(t *testing.T) { homeDir := t.TempDir() t.Setenv("HOME", homeDir) From b5fec791086be4edd3254e9e3454f41a62fd7d58 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 11 Jun 2026 16:33:18 +0100 Subject: [PATCH 4/4] test(skill): fix test case name Signed-off-by: Babak K. Shandiz --- internal/skills/registry/registry_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/skills/registry/registry_test.go b/internal/skills/registry/registry_test.go index 6bb646023ce..b7a8e9a64f5 100644 --- a/internal/skills/registry/registry_test.go +++ b/internal/skills/registry/registry_test.go @@ -83,7 +83,7 @@ func TestInstallDir(t *testing.T) { wantDir: filepath.Join("/home/monalisa", ".claude", "skills"), }, { - name: "claude code user scope", + name: "claude code user scope, respect env var", setup: func(t *testing.T) { t.Setenv("CLAUDE_CONFIG_DIR", filepath.Join("/home", "monalisa", ".config", "claude")) },