refactor: unify action into single composite step with run.ts entrypoint (#898)

* refactor: unify action into single composite step with run.ts entrypoint

Consolidate the prepare and base-action phases into a single composite
step that runs src/entrypoints/run.ts. This simplifies the action.yml
from multiple steps to one execution step, while keeping the same
behavior.

Key changes:
- Add src/entrypoints/run.ts as unified entrypoint
- Simplify action.yml to single 'Run Claude Code Action' step
- Pass all inputs via environment variables
- Update base-action to accept inputs via env vars
- Support agent mode auto-detection from prompt input

* refactor: keep SSH signing cleanup and token revocation as separate action steps

Move SSH signing key cleanup and app token revocation back to separate
composite action steps in action.yml with always() conditions, rather
than handling them inside run.ts. This keeps these cleanup concerns
as independently visible steps in the workflow.

* fix: address PR review feedback

- Use path.dirname() instead of manual string slicing for executable path
- Differentiate prepare vs execution errors in catch block so tracking
  comment accurately reflects which phase failed
- Update CLAUDE.md architecture docs to reflect unified run.ts entrypoint
  and four-phase design

* fix: address PR review feedback

- Use path.dirname() instead of manual string slicing for executable path
- Differentiate prepare vs execution errors in catch block so tracking
  comment accurately reflects which phase failed
- Rewrite CLAUDE.md to focus on mental model, key concepts, and gotchas
  instead of exhaustive file listings
This commit is contained in:
Ashwin Bhat
2026-02-03 20:09:43 -08:00
committed by GitHub
parent 6867bb3ab0
commit 9a3c761f54
17 changed files with 690 additions and 515 deletions
+13 -1
View File
@@ -28,7 +28,7 @@ async function run() {
promptFile: process.env.INPUT_PROMPT_FILE || "",
});
await runClaude(promptConfig.path, {
const result = await runClaude(promptConfig.path, {
claudeArgs: process.env.INPUT_CLAUDE_ARGS,
allowedTools: process.env.INPUT_ALLOWED_TOOLS,
disallowedTools: process.env.INPUT_DISALLOWED_TOOLS,
@@ -42,6 +42,18 @@ async function run() {
process.env.INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE,
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
});
// Set outputs for the standalone base-action
core.setOutput("conclusion", result.conclusion);
if (result.executionFile) {
core.setOutput("execution_file", result.executionFile);
}
if (result.sessionId) {
core.setOutput("session_id", result.sessionId);
}
if (result.structuredOutput) {
core.setOutput("structured_output", result.structuredOutput);
}
} catch (error) {
core.setFailed(`Action failed with error: ${error}`);
core.setOutput("conclusion", "failure");
+33 -17
View File
@@ -9,6 +9,13 @@ import type {
} from "@anthropic-ai/claude-agent-sdk";
import type { ParsedSdkOptions } from "./parse-sdk-options";
export type ClaudeRunResult = {
executionFile?: string;
sessionId?: string;
conclusion: "success" | "failure";
structuredOutput?: string;
};
const EXECUTION_FILE = `${process.env.RUNNER_TEMP}/claude-execution-output.json`;
/** Filename for the user request file, written by prompt generation */
@@ -129,7 +136,7 @@ function sanitizeSdkOutput(
export async function runClaudeWithSdk(
promptPath: string,
{ sdkOptions, showFullOutput, hasJsonSchema }: ParsedSdkOptions,
): Promise<void> {
): Promise<ClaudeRunResult> {
// Create prompt configuration - may be a string or multi-block message
const prompt = await createPromptConfig(promptPath, showFullOutput);
@@ -165,36 +172,38 @@ export async function runClaudeWithSdk(
}
} catch (error) {
console.error("SDK execution error:", error);
core.setOutput("conclusion", "failure");
process.exit(1);
throw new Error(`SDK execution error: ${error}`);
}
const result: ClaudeRunResult = {
conclusion: "failure",
};
// Write execution file
try {
await writeFile(EXECUTION_FILE, JSON.stringify(messages, null, 2));
console.log(`Log saved to ${EXECUTION_FILE}`);
core.setOutput("execution_file", EXECUTION_FILE);
result.executionFile = EXECUTION_FILE;
} catch (error) {
core.warning(`Failed to write execution file: ${error}`);
}
// Extract and set session_id from system.init message
// Extract session_id from system.init message
const initMessage = messages.find(
(m) => m.type === "system" && "subtype" in m && m.subtype === "init",
);
if (initMessage && "session_id" in initMessage && initMessage.session_id) {
core.setOutput("session_id", initMessage.session_id);
core.info(`Set session_id: ${initMessage.session_id}`);
result.sessionId = initMessage.session_id as string;
core.info(`Set session_id: ${result.sessionId}`);
}
if (!resultMessage) {
core.setOutput("conclusion", "failure");
core.error("No result message received from Claude");
process.exit(1);
throw new Error("No result message received from Claude");
}
const isSuccess = resultMessage.subtype === "success";
core.setOutput("conclusion", isSuccess ? "success" : "failure");
result.conclusion = isSuccess ? "success" : "failure";
// Handle structured output
if (hasJsonSchema) {
@@ -203,10 +212,7 @@ export async function runClaudeWithSdk(
"structured_output" in resultMessage &&
resultMessage.structured_output
) {
const structuredOutputJson = JSON.stringify(
resultMessage.structured_output,
);
core.setOutput("structured_output", structuredOutputJson);
result.structuredOutput = JSON.stringify(resultMessage.structured_output);
core.info(
`Set structured_output with ${Object.keys(resultMessage.structured_output as object).length} field(s)`,
);
@@ -214,8 +220,10 @@ export async function runClaudeWithSdk(
core.setFailed(
`--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`,
);
core.setOutput("conclusion", "failure");
process.exit(1);
result.conclusion = "failure";
throw new Error(
`--json-schema was provided but Claude did not return structured_output. Result subtype: ${resultMessage.subtype}`,
);
}
}
@@ -223,6 +231,14 @@ export async function runClaudeWithSdk(
if ("errors" in resultMessage && resultMessage.errors) {
core.error(`Execution failed: ${resultMessage.errors.join(", ")}`);
}
process.exit(1);
throw new Error(
`Claude execution failed: ${
"errors" in resultMessage && resultMessage.errors
? resultMessage.errors.join(", ")
: "unknown error"
}`,
);
}
return result;
}
+5 -1
View File
@@ -1,4 +1,5 @@
import { runClaudeWithSdk } from "./run-claude-sdk";
import type { ClaudeRunResult } from "./run-claude-sdk";
import { parseSdkOptions } from "./parse-sdk-options";
export type ClaudeOptions = {
@@ -15,7 +16,10 @@ export type ClaudeOptions = {
showFullOutput?: string;
};
export async function runClaude(promptPath: string, options: ClaudeOptions) {
export async function runClaude(
promptPath: string,
options: ClaudeOptions,
): Promise<ClaudeRunResult> {
const parsedOptions = parseSdkOptions(options);
return runClaudeWithSdk(promptPath, parsedOptions);
}