Files
claude-code-action/src/entrypoints/run.ts
Ashwin Bhat a3c96305c1 fix: move critical outputs to immediately after prepare step
Previously, outputs like GITHUB_TOKEN and branch_name were set after Claude execution completed. This caused issues if Claude failed, as downstream cleanup steps wouldn't have access to these outputs.

Now outputs are set immediately after prepare() completes, ensuring they're available even if Claude execution fails.
2025-08-06 08:24:21 -07:00

146 lines
5.2 KiB
TypeScript

#!/usr/bin/env bun
/**
* Unified entrypoint that combines prepare and run-claude steps
*/
import * as core from "@actions/core";
import { setupGitHubToken } from "../github/token";
import { checkWritePermissions } from "../github/validation/permissions";
import { createOctokit } from "../github/api/client";
import { parseGitHubContext, isEntityContext } from "../github/context";
import { getMode, isValidMode, DEFAULT_MODE } from "../modes/registry";
import type { ModeName } from "../modes/types";
import { prepare } from "../prepare";
import { runClaudeCore } from "../../base-action/src/run-claude-core";
import { validateEnvironmentVariables } from "../../base-action/src/validate-env";
import { setupClaudeCodeSettings } from "../../base-action/src/setup-claude-code-settings";
async function run() {
try {
// Step 1: Get mode first to determine authentication method
const modeInput = process.env.MODE || DEFAULT_MODE;
// Validate mode input
if (!isValidMode(modeInput)) {
throw new Error(`Invalid mode: ${modeInput}`);
}
const validatedMode: ModeName = modeInput;
// Step 2: Setup GitHub token based on mode
let githubToken: string;
if (validatedMode === "experimental-review") {
// For experimental-review mode, use the default GitHub Action token
githubToken = process.env.DEFAULT_WORKFLOW_TOKEN || "";
if (!githubToken) {
throw new Error(
"DEFAULT_WORKFLOW_TOKEN not found for experimental-review mode",
);
}
console.log("Using default GitHub Action token for review mode");
} else {
// For other modes, use the existing token exchange
githubToken = await setupGitHubToken();
}
const octokit = createOctokit(githubToken);
// Step 3: Parse GitHub context (once for all operations)
const context = parseGitHubContext();
// Step 4: Check write permissions (only for entity contexts)
if (isEntityContext(context)) {
const hasWritePermissions = await checkWritePermissions(
octokit.rest,
context,
);
if (!hasWritePermissions) {
throw new Error(
"Actor does not have write permissions to the repository",
);
}
}
// Step 5: Get mode and check trigger conditions
const mode = getMode(validatedMode, context);
const containsTrigger = mode.shouldTrigger(context);
// Set output for action.yml to check (in case it's still needed)
core.setOutput("contains_trigger", containsTrigger.toString());
if (!containsTrigger) {
console.log("No trigger found, skipping remaining steps");
return;
}
// Step 6: Use the modular prepare function
const prepareResult = await prepare({
context,
octokit,
mode,
githubToken,
});
// Set critical outputs immediately after prepare completes
// This ensures they're available for cleanup even if Claude fails
core.setOutput("GITHUB_TOKEN", githubToken);
core.setOutput("mcp_config", prepareResult.mcpConfig);
if (prepareResult.branchInfo.claudeBranch) {
core.setOutput("branch_name", prepareResult.branchInfo.claudeBranch);
core.setOutput("CLAUDE_BRANCH", prepareResult.branchInfo.claudeBranch);
}
core.setOutput("BASE_BRANCH", prepareResult.branchInfo.baseBranch);
if (prepareResult.commentId) {
core.setOutput("claude_comment_id", prepareResult.commentId.toString());
}
// Step 7: The mode.prepare() call already created the prompt and set up tools
// We need to get the allowed/disallowed tools from environment variables
// TODO: Update Mode interface to return tools from prepare() instead of relying on env vars
const allowedTools = process.env.ALLOWED_TOOLS || "";
const disallowedTools = process.env.DISALLOWED_TOOLS || "";
const promptFile = `${process.env.RUNNER_TEMP}/claude-prompts/claude-prompt.txt`;
// Step 8: Validate environment and setup Claude settings
validateEnvironmentVariables();
await setupClaudeCodeSettings(process.env.SETTINGS);
// Step 9: Run Claude Code
console.log("Running Claude Code...");
// Build environment object to pass to Claude
const claudeEnvObject: Record<string, string> = {
GITHUB_TOKEN: githubToken,
NODE_VERSION: process.env.NODE_VERSION || "18.x",
DETAILED_PERMISSION_MESSAGES: "1",
CLAUDE_CODE_ACTION: "1",
};
await runClaudeCore({
promptFile,
settings: process.env.SETTINGS,
allowedTools,
disallowedTools,
maxTurns: process.env.MAX_TURNS,
mcpConfig: prepareResult.mcpConfig,
systemPrompt: "",
appendSystemPrompt: "",
claudeEnv: process.env.CLAUDE_ENV,
fallbackModel: process.env.FALLBACK_MODEL,
model: process.env.ANTHROPIC_MODEL || process.env.MODEL,
timeoutMinutes: process.env.TIMEOUT_MINUTES || "30",
env: claudeEnvObject,
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
core.setFailed(`Action failed with error: ${errorMessage}`);
// Also output the clean error message for the action to capture
core.setOutput("prepare_error", errorMessage);
core.setOutput("conclusion", "failure");
process.exit(1);
}
}
if (import.meta.main) {
run();
}