From dc65f4ac98d71813b5361d5266d0d0082e367b30 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Mon, 25 Aug 2025 08:11:15 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20add=20path=5Fto=5Fclaude=5Fcode=5Fexecu?= =?UTF-8?q?table=20input=20for=20custom=20Claude=20Code=E2=80=A6=20(#480)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add path_to_claude_code_executable input for custom Claude Code installations Adds optional input to specify a custom Claude Code executable path, bypassing automatic installation. This enables: - Using pre-installed Claude Code binaries - Testing with specific versions for debugging - Custom installation paths in unique environments Includes warning that older versions may cause compatibility issues with new features. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * test: add workflow to test custom Claude Code executable path Adds test workflow that: - Manually installs Claude Code via install script - Uses the new path_to_claude_code_executable input - Verifies the custom executable path works correctly - Validates output and execution success 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- .github/workflows/test-custom-executable.yml | 84 ++++++++++++++++++++ action.yml | 20 ++++- base-action/action.yml | 16 +++- base-action/src/index.ts | 2 + base-action/src/run-claude.ts | 6 +- 5 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/test-custom-executable.yml diff --git a/.github/workflows/test-custom-executable.yml b/.github/workflows/test-custom-executable.yml new file mode 100644 index 0000000..cf38c97 --- /dev/null +++ b/.github/workflows/test-custom-executable.yml @@ -0,0 +1,84 @@ +name: Test Custom Claude Code Executable + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + test-custom-executable: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Install Claude Code manually + run: | + echo "Installing Claude Code using install script..." + curl -fsSL https://claude.ai/install.sh | bash -s latest + echo "Claude Code installed at: $HOME/.local/bin/claude" + + # Verify 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 custom executable path + id: custom-exe-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 + allowed_tools: "LS,Read" + timeout_minutes: "3" + + - name: Verify custom executable output + run: | + OUTPUT_FILE="${{ steps.custom-exe-test.outputs.execution_file }}" + CONCLUSION="${{ steps.custom-exe-test.outputs.conclusion }}" + + echo "Conclusion: $CONCLUSION" + echo "Output file: $OUTPUT_FILE" + + if [ "$CONCLUSION" = "success" ]; then + echo "✅ Action completed successfully with custom executable" + else + echo "❌ Action failed with custom executable" + exit 1 + fi + + if [ -f "$OUTPUT_FILE" ]; then + if [ -s "$OUTPUT_FILE" ]; then + echo "✅ Execution log file created successfully with content" + echo "Validating JSON format:" + if jq . "$OUTPUT_FILE" > /dev/null 2>&1; then + echo "✅ Output is valid JSON" + echo "Content preview:" + head -c 500 "$OUTPUT_FILE" + echo "" + + # 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 is empty" + exit 1 + fi + else + echo "❌ Execution log file not found" + exit 1 + fi diff --git a/action.yml b/action.yml index abda69e..0ebaaf4 100644 --- a/action.yml +++ b/action.yml @@ -118,6 +118,10 @@ 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: "" outputs: execution_file: @@ -177,9 +181,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.90 - 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 != '' @@ -214,6 +227,7 @@ runs: INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }} 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 }} # Model configuration ANTHROPIC_MODEL: ${{ inputs.model || inputs.anthropic_model }} diff --git a/base-action/action.yml b/base-action/action.yml index 2e462d1..c073555 100644 --- a/base-action/action.yml +++ b/base-action/action.yml @@ -87,6 +87,10 @@ 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: "" outputs: conclusion: @@ -118,7 +122,16 @@ runs: - name: Install Claude Code shell: bash - run: curl -fsSL https://claude.ai/install.sh | bash -s 1.0.90 + 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 @@ -147,6 +160,7 @@ runs: INPUT_CLAUDE_ENV: ${{ inputs.claude_env }} INPUT_FALLBACK_MODEL: ${{ inputs.fallback_model }} INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ inputs.experimental_slash_commands_dir }} + INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_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 f4d3724..0675ff9 100644 --- a/base-action/src/index.ts +++ b/base-action/src/index.ts @@ -31,6 +31,8 @@ async function run() { 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 1d095b7..2bd4af2 100644 --- a/base-action/src/run-claude.ts +++ b/base-action/src/run-claude.ts @@ -22,6 +22,7 @@ export type ClaudeOptions = { fallbackModel?: string; timeoutMinutes?: string; model?: string; + pathToClaudeCodeExecutable?: string; }; type PreparedConfig = { @@ -168,7 +169,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,