From 632f04bbcfe8159bffc1cd86bcd9a0764ecb932a Mon Sep 17 00:00:00 2001 From: km-anthropic Date: Thu, 14 Aug 2025 15:29:05 -0700 Subject: [PATCH] feat: Copy project subagents to Claude runtime environment Enables custom subagents defined in .claude/agents/ to work in GitHub Actions by: - Checking for project agents in GITHUB_WORKSPACE/.claude/agents/ - Creating ~/.claude/agents/ directory if needed - Copying all .md agent files to Claude's runtime location - Following same pattern as slash commands for consistency Includes comprehensive test coverage for the new functionality. --- base-action/src/setup-claude-code-settings.ts | 27 ++++++++ .../test/setup-claude-code-settings.test.ts | 66 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/base-action/src/setup-claude-code-settings.ts b/base-action/src/setup-claude-code-settings.ts index 6c40cfe..ba252e9 100644 --- a/base-action/src/setup-claude-code-settings.ts +++ b/base-action/src/setup-claude-code-settings.ts @@ -79,4 +79,31 @@ export async function setupClaudeCodeSettings( console.log(`Slash commands directory not found or error copying: ${e}`); } } + + // Copy project subagents to Claude's agents directory + // Use GITHUB_WORKSPACE if available (set by GitHub Actions), otherwise use current directory + const workspaceDir = process.env.GITHUB_WORKSPACE || process.cwd(); + const projectAgentsDir = `${workspaceDir}/.claude/agents`; + const claudeAgentsDir = `${home}/.claude/agents`; + + try { + await $`test -d ${projectAgentsDir}`.quiet(); + console.log(`Found project agents directory at ${projectAgentsDir}`); + + // Ensure target agents directory exists + await $`mkdir -p ${claudeAgentsDir}`.quiet(); + + // Copy all .md files from project agents to Claude's agents directory + await $`cp ${projectAgentsDir}/*.md ${claudeAgentsDir}/ 2>/dev/null || true`.quiet(); + + // Count copied agents for logging + const agentFiles = await $`ls ${claudeAgentsDir}/*.md 2>/dev/null | wc -l` + .quiet() + .text(); + const agentCount = parseInt(agentFiles.trim()) || 0; + console.log(`Copied ${agentCount} agent(s) to ${claudeAgentsDir}`); + } catch (e) { + // Directory doesn't exist or no agents to copy - this is expected in most cases + console.log(`No project agents directory found at ${projectAgentsDir}`); + } } diff --git a/base-action/test/setup-claude-code-settings.test.ts b/base-action/test/setup-claude-code-settings.test.ts index 19cf0cd..63bab19 100644 --- a/base-action/test/setup-claude-code-settings.test.ts +++ b/base-action/test/setup-claude-code-settings.test.ts @@ -215,4 +215,70 @@ describe("setupClaudeCodeSettings", () => { const settingsContent = await readFile(settingsPath, "utf-8"); expect(JSON.parse(settingsContent).enableAllProjectMcpServers).toBe(true); }); + + test("should copy project agents when .claude/agents directory exists", async () => { + // Create a mock project structure with agents + const projectDir = join(testHomeDir, "test-project"); + const projectAgentsDir = join(projectDir, ".claude", "agents"); + await mkdir(projectAgentsDir, { recursive: true }); + + // Create test agent files + await writeFile( + join(projectAgentsDir, "test-agent.md"), + "---\nname: test-agent\ndescription: Test agent\n---\nTest agent content", + ); + await writeFile( + join(projectAgentsDir, "another-agent.md"), + "---\nname: another-agent\n---\nAnother agent", + ); + + // Set GITHUB_WORKSPACE to the test project directory + const originalWorkspace = process.env.GITHUB_WORKSPACE; + process.env.GITHUB_WORKSPACE = projectDir; + + try { + await setupClaudeCodeSettings(undefined, testHomeDir); + + // Check that agents were copied + const agentsDir = join(testHomeDir, ".claude", "agents"); + const files = await readdir(agentsDir); + expect(files).toContain("test-agent.md"); + expect(files).toContain("another-agent.md"); + + // Verify content was copied correctly + const content = await readFile(join(agentsDir, "test-agent.md"), "utf-8"); + expect(content).toContain("Test agent content"); + } finally { + // Restore original GITHUB_WORKSPACE + if (originalWorkspace !== undefined) { + process.env.GITHUB_WORKSPACE = originalWorkspace; + } else { + delete process.env.GITHUB_WORKSPACE; + } + } + }); + + test("should handle missing project agents directory gracefully", async () => { + // Set GITHUB_WORKSPACE to a directory without .claude/agents + const projectDir = join(testHomeDir, "project-without-agents"); + await mkdir(projectDir, { recursive: true }); + + const originalWorkspace = process.env.GITHUB_WORKSPACE; + process.env.GITHUB_WORKSPACE = projectDir; + + try { + await setupClaudeCodeSettings(undefined, testHomeDir); + + // Should complete without errors + const settingsContent = await readFile(settingsPath, "utf-8"); + const settings = JSON.parse(settingsContent); + expect(settings.enableAllProjectMcpServers).toBe(true); + } finally { + if (originalWorkspace !== undefined) { + process.env.GITHUB_WORKSPACE = originalWorkspace; + } else { + delete process.env.GITHUB_WORKSPACE; + } + } + }); });