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,