Attempt to make this work

This commit is contained in:
Mark Wylde
2025-05-31 00:16:57 +01:00
parent 3afac506b2
commit 44d513b712
3 changed files with 11 additions and 377 deletions

View File

@@ -83,17 +83,6 @@ runs:
cd ${{ github.action_path }}
bun install
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Claude Code CLI
shell: bash
run: |
# Install Claude Code CLI via npm
npm install -g @anthropic-ai/claude-code
- name: Prepare action
id: prepare
shell: bash
@@ -114,9 +103,17 @@ runs:
- name: Run Claude Code
id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true'
shell: bash
run: |
bun run ${{ github.action_path }}/src/entrypoints/execute-claude.ts
uses: anthropics/claude-code-base-action@c8e31bd52d9a149b3f8309d7978c6edaa282688d # v0.0.8
with:
prompt_file: /tmp/claude-prompts/claude-prompt.txt
allowed_tools: ${{ env.ALLOWED_TOOLS }}
disallowed_tools: ${{ env.DISALLOWED_TOOLS }}
timeout_minutes: ${{ inputs.timeout_minutes }}
model: ${{ inputs.model || inputs.anthropic_model }}
mcp_config: ${{ steps.prepare.outputs.mcp_config }}
use_bedrock: ${{ inputs.use_bedrock }}
use_vertex: ${{ inputs.use_vertex }}
anthropic_api_key: ${{ inputs.anthropic_api_key }}
env:
# Core configuration
PROMPT_FILE: /tmp/claude-prompts/claude-prompt.txt

View File

@@ -1,302 +0,0 @@
#!/usr/bin/env bun
import Anthropic from "@anthropic-ai/sdk";
import * as fs from "fs";
import { spawn } from "child_process";
import { promisify } from "util";
export interface ClaudeExecutorConfig {
apiKey?: string;
model?: string;
promptFile?: string;
prompt?: string;
maxTurns?: number;
timeoutMinutes?: number;
mcpConfig?: string;
allowedTools?: string;
disallowedTools?: string;
useBedrock?: boolean;
useVertex?: boolean;
}
export interface ClaudeExecutorResult {
conclusion: "success" | "failure";
executionFile?: string;
error?: string;
}
interface MCPServer {
command: string;
args: string[];
env?: Record<string, string>;
}
interface MCPConfig {
mcpServers: Record<string, MCPServer>;
}
export class ClaudeExecutor {
private config: ClaudeExecutorConfig;
constructor(config: ClaudeExecutorConfig) {
this.config = config;
}
private async readPrompt(): Promise<string> {
console.log("[CLAUDE-EXECUTOR] Reading prompt...");
if (this.config.prompt) {
console.log("[CLAUDE-EXECUTOR] Using direct prompt");
return this.config.prompt;
}
if (this.config.promptFile) {
console.log(`[CLAUDE-EXECUTOR] Using prompt file: ${this.config.promptFile}`);
if (!fs.existsSync(this.config.promptFile)) {
console.error(`[CLAUDE-EXECUTOR] Prompt file not found: ${this.config.promptFile}`);
throw new Error(`Prompt file not found: ${this.config.promptFile}`);
}
const content = fs.readFileSync(this.config.promptFile, "utf-8");
console.log(`[CLAUDE-EXECUTOR] Prompt file size: ${content.length} bytes`);
return content;
}
console.error("[CLAUDE-EXECUTOR] Neither prompt nor promptFile provided");
throw new Error("Either prompt or promptFile must be provided");
}
private parseTools(): { allowed: string[]; disallowed: string[] } {
const allowed = this.config.allowedTools
? this.config.allowedTools
.split(",")
.map((t) => t.trim())
.filter(Boolean)
: [];
const disallowed = this.config.disallowedTools
? this.config.disallowedTools
.split(",")
.map((t) => t.trim())
.filter(Boolean)
: [];
return { allowed, disallowed };
}
private createExecutionLog(result: any, error?: string): string {
const logData = {
conclusion: error ? "failure" : "success",
model: this.config.model || "claude-3-7-sonnet-20250219",
timestamp: new Date().toISOString(),
result,
error,
};
const logFile = "/tmp/claude-execution.json";
fs.writeFileSync(logFile, JSON.stringify(logData, null, 2));
return logFile;
}
private async setupMCPConfig(): Promise<string | null> {
if (!this.config.mcpConfig) {
console.log("[CLAUDE-EXECUTOR] No MCP config provided");
return null;
}
try {
const mcpConfig: MCPConfig = JSON.parse(this.config.mcpConfig);
const configFile = "/tmp/mcp-config.json";
console.log("[CLAUDE-EXECUTOR] Setting up MCP configuration...");
console.log("[CLAUDE-EXECUTOR] MCP servers:", Object.keys(mcpConfig.mcpServers));
fs.writeFileSync(configFile, JSON.stringify(mcpConfig, null, 2));
console.log("[CLAUDE-EXECUTOR] MCP config written to:", configFile);
return configFile;
} catch (error) {
console.error("[CLAUDE-EXECUTOR] Failed to parse MCP config:", error);
return null;
}
}
async execute(): Promise<ClaudeExecutorResult> {
try {
const prompt = await this.readPrompt();
const tools = this.parseTools();
console.log(`[CLAUDE-EXECUTOR] Prompt length: ${prompt.length} characters`);
console.log(
`[CLAUDE-EXECUTOR] Executing Claude with model: ${this.config.model || "claude-3-7-sonnet-20250219"}`,
);
console.log(`[CLAUDE-EXECUTOR] Allowed tools: ${tools.allowed.join(", ") || "none"}`);
console.log(`[CLAUDE-EXECUTOR] Disallowed tools: ${tools.disallowed.join(", ") || "none"}`);
// Setup MCP configuration if provided
const mcpConfigFile = await this.setupMCPConfig();
if (mcpConfigFile) {
console.log("[CLAUDE-EXECUTOR] Using Claude Code with MCP configuration");
return await this.executeWithMCP(prompt, mcpConfigFile, tools);
} else {
console.log("[CLAUDE-EXECUTOR] No MCP config provided, falling back to basic API");
return await this.executeBasicAPI(prompt);
}
} catch (error) {
console.error("[CLAUDE-EXECUTOR] Claude execution failed:", error);
const executionFile = this.createExecutionLog(null, String(error));
return {
conclusion: "failure",
executionFile,
error: String(error),
};
}
}
private async executeWithMCP(
prompt: string,
mcpConfigFile: string,
tools: { allowed: string[]; disallowed: string[] }
): Promise<ClaudeExecutorResult> {
console.log("[CLAUDE-EXECUTOR] Starting Claude Code with MCP support...");
if (!this.config.apiKey) {
throw new Error("Anthropic API key is required");
}
// Write prompt to a temporary file
const promptFile = "/tmp/claude-prompt.txt";
fs.writeFileSync(promptFile, prompt);
// Build Claude Code command arguments
const args = [
"-p", `@${promptFile}`
];
// Add MCP config if available
if (mcpConfigFile) {
args.push("--mcp-config", mcpConfigFile);
}
// Add model if specified
if (this.config.model) {
args.push("--model", this.config.model);
}
// Add allowed tools if specified
if (tools.allowed.length > 0) {
args.push("--allowedTools", tools.allowed.join(","));
}
// Add disallowed tools if specified
if (tools.disallowed.length > 0) {
args.push("--disallowedTools", tools.disallowed.join(","));
}
console.log("[CLAUDE-EXECUTOR] Claude Code command:", "claude", args.join(" "));
return new Promise((resolve) => {
const claude = spawn("claude", args, {
env: {
...process.env,
ANTHROPIC_API_KEY: this.config.apiKey,
},
});
let stdout = "";
let stderr = "";
claude.stdout?.on("data", (data) => {
const chunk = data.toString();
stdout += chunk;
console.log("[CLAUDE-STDOUT]", chunk.trim());
});
claude.stderr?.on("data", (data) => {
const chunk = data.toString();
stderr += chunk;
console.log("[CLAUDE-STDERR]", chunk.trim());
});
claude.on("close", (code) => {
console.log(`[CLAUDE-EXECUTOR] Claude Code process exited with code ${code}`);
const result = {
stdout,
stderr,
exitCode: code,
success: code === 0,
};
const executionFile = this.createExecutionLog(result, code !== 0 ? `Process exited with code ${code}` : undefined);
resolve({
conclusion: code === 0 ? "success" : "failure",
executionFile,
error: code !== 0 ? `Claude Code exited with code ${code}: ${stderr}` : undefined,
});
});
claude.on("error", (error) => {
console.error("[CLAUDE-EXECUTOR] Failed to spawn Claude Code process:", error);
const executionFile = this.createExecutionLog(null, String(error));
resolve({
conclusion: "failure",
executionFile,
error: String(error),
});
});
});
}
private async executeBasicAPI(prompt: string): Promise<ClaudeExecutorResult> {
console.log("[CLAUDE-EXECUTOR] WARNING: Using simple Anthropic API - MCP tools not supported in this implementation");
console.log("[CLAUDE-EXECUTOR] This explains why Claude cannot use mcp__local_git_ops tools");
if (!this.config.apiKey) {
throw new Error("Anthropic API key is required");
}
const anthropic = new Anthropic({
apiKey: this.config.apiKey,
});
console.log("[CLAUDE-EXECUTOR] Starting simple Anthropic API call...");
console.log("[CLAUDE-EXECUTOR] Prompt preview:", prompt.substring(0, 500) + "...");
// Create a simple message request
const response = await anthropic.messages.create({
model: this.config.model || "claude-3-7-sonnet-20250219",
max_tokens: 8192,
messages: [
{
role: "user",
content: prompt,
},
],
});
console.log("[CLAUDE-EXECUTOR] Claude response received successfully");
console.log("[CLAUDE-EXECUTOR] Response type:", response.content[0]?.type);
if (response.content[0]?.type === "text") {
console.log("[CLAUDE-EXECUTOR] Response preview:", response.content[0].text.substring(0, 500) + "...");
}
const executionFile = this.createExecutionLog(response);
return {
conclusion: "success",
executionFile,
};
}
}
export async function runClaude(
config: ClaudeExecutorConfig,
): Promise<ClaudeExecutorResult> {
const executor = new ClaudeExecutor(config);
return await executor.execute();
}

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env bun
import * as core from "@actions/core";
import { runClaude, type ClaudeExecutorConfig } from "../claude/executor";
async function main() {
try {
console.log("[EXECUTE-CLAUDE] Starting execute-claude.ts entry point...");
console.log(`[EXECUTE-CLAUDE] ANTHROPIC_API_KEY: ${process.env.ANTHROPIC_API_KEY ? '***' : 'undefined'}`);
console.log(`[EXECUTE-CLAUDE] MODEL: ${process.env.MODEL || 'undefined'}`);
console.log(`[EXECUTE-CLAUDE] ANTHROPIC_MODEL: ${process.env.ANTHROPIC_MODEL || 'undefined'}`);
console.log(`[EXECUTE-CLAUDE] PROMPT_FILE: ${process.env.PROMPT_FILE || 'undefined'}`);
console.log(`[EXECUTE-CLAUDE] ALLOWED_TOOLS: ${process.env.ALLOWED_TOOLS || 'undefined'}`);
console.log(`[EXECUTE-CLAUDE] DISALLOWED_TOOLS: ${process.env.DISALLOWED_TOOLS || 'undefined'}`);
console.log(`[EXECUTE-CLAUDE] MCP_CONFIG length: ${process.env.MCP_CONFIG?.length || 0}`);
console.log(`[EXECUTE-CLAUDE] USE_BEDROCK: ${process.env.USE_BEDROCK}`);
console.log(`[EXECUTE-CLAUDE] USE_VERTEX: ${process.env.USE_VERTEX}`);
const config: ClaudeExecutorConfig = {
apiKey: process.env.ANTHROPIC_API_KEY,
model: process.env.ANTHROPIC_MODEL || process.env.MODEL,
promptFile: process.env.PROMPT_FILE,
prompt: process.env.PROMPT,
maxTurns: process.env.MAX_TURNS
? parseInt(process.env.MAX_TURNS)
: undefined,
timeoutMinutes: process.env.TIMEOUT_MINUTES
? parseInt(process.env.TIMEOUT_MINUTES)
: 30,
mcpConfig: process.env.MCP_CONFIG,
allowedTools: process.env.ALLOWED_TOOLS,
disallowedTools: process.env.DISALLOWED_TOOLS,
useBedrock: process.env.USE_BEDROCK === "true",
useVertex: process.env.USE_VERTEX === "true",
};
console.log("[EXECUTE-CLAUDE] Configuration prepared, starting Claude execution...");
const result = await runClaude(config);
// Set outputs for GitHub Actions
core.setOutput("conclusion", result.conclusion);
if (result.executionFile) {
core.setOutput("execution_file", result.executionFile);
}
if (result.conclusion === "failure") {
core.setFailed(result.error || "Claude execution failed");
} else {
console.log("Claude execution completed successfully");
}
} catch (error) {
console.error("Failed to execute Claude:", error);
core.setFailed(`Failed to execute Claude: ${error}`);
}
}
main().catch((error) => {
console.error("Unhandled error:", error);
core.setFailed(`Unhandled error: ${error}`);
process.exit(1);
});