diff --git a/.github/workflows/test-custom-executables.yml b/.github/workflows/test-custom-executables.yml new file mode 100644 index 0000000..e05f71f --- /dev/null +++ b/.github/workflows/test-custom-executables.yml @@ -0,0 +1,90 @@ +name: Test Custom Executables + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + test-custom-executables: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Install Bun manually + run: | + echo "Installing Bun..." + curl -fsSL https://bun.sh/install | bash + echo "Bun installed at: $HOME/.bun/bin/bun" + + # Verify Bun installation + if [ -f "$HOME/.bun/bin/bun" ]; then + echo "✅ Bun executable found" + $HOME/.bun/bin/bun --version + else + echo "❌ Bun executable not found" + exit 1 + fi + + - name: Install Claude Code manually + run: | + echo "Installing Claude Code..." + curl -fsSL https://claude.ai/install.sh | bash -s latest + echo "Claude Code installed at: $HOME/.local/bin/claude" + + # Verify Claude installation + if [ -f "$HOME/.local/bin/claude" ]; then + echo "✅ Claude executable found" + ls -la "$HOME/.local/bin/claude" + else + echo "❌ Claude executable not found" + exit 1 + fi + + - name: Test with both custom executables + id: custom-test + uses: ./base-action + with: + prompt: | + List the files in the current directory starting with "package" + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + path_to_claude_code_executable: /home/runner/.local/bin/claude + path_to_bun_executable: /home/runner/.bun/bin/bun + allowed_tools: "LS,Read" + timeout_minutes: "3" + + - name: Verify custom executables worked + run: | + OUTPUT_FILE="${{ steps.custom-test.outputs.execution_file }}" + CONCLUSION="${{ steps.custom-test.outputs.conclusion }}" + + echo "Conclusion: $CONCLUSION" + echo "Output file: $OUTPUT_FILE" + + if [ "$CONCLUSION" = "success" ]; then + echo "✅ Action completed successfully with both custom executables" + else + echo "❌ Action failed with custom executables" + exit 1 + fi + + if [ -f "$OUTPUT_FILE" ] && [ -s "$OUTPUT_FILE" ]; then + echo "✅ Execution log file created successfully" + if jq . "$OUTPUT_FILE" > /dev/null 2>&1; then + echo "✅ Output is valid JSON" + # Verify the task was completed + if grep -q "package" "$OUTPUT_FILE"; then + echo "✅ Claude successfully listed package files" + else + echo "⚠️ Could not verify if package files were listed" + fi + else + echo "❌ Output is not valid JSON" + exit 1 + fi + else + echo "❌ Execution log file not found or empty" + exit 1 + fi diff --git a/action.yml b/action.yml index ca11940..cd61a0d 100644 --- a/action.yml +++ b/action.yml @@ -85,6 +85,14 @@ inputs: description: "Restrict network access to these domains only (newline-separated). If not set, no restrictions are applied. Provider domains are auto-detected." required: false default: "" + path_to_claude_code_executable: + description: "Optional path to a custom Claude Code executable. If provided, skips automatic installation and uses this executable instead. WARNING: Using an older version may cause problems if the action begins taking advantage of new Claude Code features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment." + required: false + default: "" + path_to_bun_executable: + description: "Optional path to a custom Bun executable. If provided, skips automatic Bun installation and uses this executable instead. WARNING: Using an incompatible version may cause problems if the action requires specific Bun features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment." + required: false + default: "" outputs: execution_file: @@ -101,10 +109,20 @@ runs: using: "composite" steps: - name: Install Bun + if: inputs.path_to_bun_executable == '' uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2 with: bun-version: 1.2.11 + - name: Setup Custom Bun Path + if: inputs.path_to_bun_executable != '' + shell: bash + run: | + echo "Using custom Bun executable: ${{ inputs.path_to_bun_executable }}" + # Add the directory containing the custom executable to PATH + BUN_DIR=$(dirname "${{ inputs.path_to_bun_executable }}") + echo "$BUN_DIR" >> "$GITHUB_PATH" + - name: Install Dependencies shell: bash run: | @@ -144,9 +162,18 @@ runs: bun install echo "Base-action dependencies installed" cd - - # Install Claude Code globally - curl -fsSL https://claude.ai/install.sh | bash -s 1.0.86 - echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + # Install Claude Code if no custom executable is provided + if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then + echo "Installing Claude Code..." + curl -fsSL https://claude.ai/install.sh | bash -s 1.0.90 + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + else + echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}" + # Add the directory containing the custom executable to PATH + CLAUDE_DIR=$(dirname "${{ inputs.path_to_claude_code_executable }}") + echo "$CLAUDE_DIR" >> "$GITHUB_PATH" + fi - name: Setup Network Restrictions if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != '' @@ -174,6 +201,8 @@ runs: INPUT_CLAUDE_ARGS: ${{ steps.prepare.outputs.claude_args }} INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands INPUT_ACTION_INPUTS_PRESENT: ${{ steps.prepare.outputs.action_inputs_present }} + INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }} + INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }} # Model configuration GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }} diff --git a/base-action/action.yml b/base-action/action.yml index f326ed8..904bb1c 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -54,6 +54,14 @@ inputs: description: "Whether to use Node.js dependency caching (set to true only for Node.js projects with lock files)" required: false default: "false" + path_to_claude_code_executable: + description: "Optional path to a custom Claude Code executable. If provided, skips automatic installation and uses this executable instead. WARNING: Using an older version may cause problems if the action begins taking advantage of new Claude Code features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment." + required: false + default: "" + path_to_bun_executable: + description: "Optional path to a custom Bun executable. If provided, skips automatic Bun installation and uses this executable instead. WARNING: Using an incompatible version may cause problems if the action requires specific Bun features. This input is typically not needed unless you're debugging something specific or have unique needs in your environment." + required: false + default: "" outputs: conclusion: @@ -73,10 +81,20 @@ runs: cache: ${{ inputs.use_node_cache == 'true' && 'npm' || '' }} - name: Install Bun + if: inputs.path_to_bun_executable == '' uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # https://github.com/oven-sh/setup-bun/releases/tag/v2.0.2 with: bun-version: 1.2.11 + - name: Setup Custom Bun Path + if: inputs.path_to_bun_executable != '' + shell: bash + run: | + echo "Using custom Bun executable: ${{ inputs.path_to_bun_executable }}" + # Add the directory containing the custom executable to PATH + BUN_DIR=$(dirname "${{ inputs.path_to_bun_executable }}") + echo "$BUN_DIR" >> "$GITHUB_PATH" + - name: Install Dependencies shell: bash run: | @@ -85,7 +103,16 @@ runs: - name: Install Claude Code shell: bash - run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.86 + run: | + if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then + echo "Installing Claude Code..." + curl -fsSL https://claude.ai/install.sh | bash -s 1.0.90 + else + echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}" + # Add the directory containing the custom executable to PATH + CLAUDE_DIR=$(dirname "${{ inputs.path_to_claude_code_executable }}") + echo "$CLAUDE_DIR" >> "$GITHUB_PATH" + fi - name: Run Claude Code Action shell: bash @@ -106,6 +133,8 @@ runs: INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }} INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ inputs.experimental_slash_commands_dir }} + INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }} + INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }} # Provider configuration ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }} diff --git a/base-action/src/index.ts b/base-action/src/index.ts index dd467a8..8e2fae9 100644 --- a/base-action/src/index.ts +++ b/base-action/src/index.ts @@ -24,6 +24,17 @@ async function run() { await runClaude(promptConfig.path, { timeoutMinutes: process.env.INPUT_TIMEOUT_MINUTES, claudeArgs: process.env.INPUT_CLAUDE_ARGS, + allowedTools: process.env.INPUT_ALLOWED_TOOLS, + disallowedTools: process.env.INPUT_DISALLOWED_TOOLS, + maxTurns: process.env.INPUT_MAX_TURNS, + mcpConfig: process.env.INPUT_MCP_CONFIG, + systemPrompt: process.env.INPUT_SYSTEM_PROMPT, + appendSystemPrompt: process.env.INPUT_APPEND_SYSTEM_PROMPT, + claudeEnv: process.env.INPUT_CLAUDE_ENV, + fallbackModel: process.env.INPUT_FALLBACK_MODEL, + model: process.env.ANTHROPIC_MODEL, + pathToClaudeCodeExecutable: + process.env.INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE, }); } catch (error) { core.setFailed(`Action failed with error: ${error}`); diff --git a/base-action/src/run-claude.ts b/base-action/src/run-claude.ts index 290d495..b44432a 100644 --- a/base-action/src/run-claude.ts +++ b/base-action/src/run-claude.ts @@ -15,6 +15,16 @@ const BASE_ARGS = ["--verbose", "--output-format", "stream-json"]; export type ClaudeOptions = { timeoutMinutes?: string; claudeArgs?: string; + model?: string; + pathToClaudeCodeExecutable?: string; + allowedTools?: string; + disallowedTools?: string; + maxTurns?: string; + mcpConfig?: string; + systemPrompt?: string; + appendSystemPrompt?: string; + claudeEnv?: string; + fallbackModel?: string; }; type PreparedConfig = { @@ -122,7 +132,10 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) { pipeStream.destroy(); }); - const claudeProcess = spawn("claude", config.claudeArgs, { + // Use custom executable path if provided, otherwise default to "claude" + const claudeExecutable = options.pathToClaudeCodeExecutable || "claude"; + + const claudeProcess = spawn(claudeExecutable, config.claudeArgs, { stdio: ["pipe", "pipe", "inherit"], env: { ...process.env,