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
This commit is contained in:
Ashwin Bhat 2026-02-03 18:50:20 -08:00
parent 6e5825aa53
commit 0f45be3f67
No known key found for this signature in database
2 changed files with 30 additions and 18 deletions

View File

@ -25,28 +25,35 @@ bun run typecheck # Run TypeScript type checker
## Architecture Overview ## Architecture Overview
This is a GitHub Action that enables Claude to interact with GitHub PRs and issues. The action operates in two main phases: This is a GitHub Action that enables Claude to interact with GitHub PRs and issues. The action runs through a unified entrypoint (`src/entrypoints/run.ts`) that orchestrates four internal phases within a single composite step:
### Phase 1: Preparation (`src/entrypoints/prepare.ts`) ### Phase 1: Prepare
1. **Authentication Setup**: Establishes GitHub token via OIDC or GitHub App 1. **Authentication Setup**: Establishes GitHub token via OIDC or GitHub App
2. **Permission Validation**: Verifies actor has write permissions 2. **Permission Validation**: Verifies actor has write permissions
3. **Trigger Detection**: Uses mode-specific logic to determine if Claude should respond 3. **Trigger Detection**: Uses mode-specific logic to determine if Claude should respond
4. **Context Creation**: Prepares GitHub context and initial tracking comment 4. **Context Creation**: Prepares GitHub context, initial tracking comment, and branch
### Phase 2: Execution (`base-action/`) ### Phase 2: Install
The `base-action/` directory contains the core Claude Code execution logic, which serves a dual purpose: 1. **Claude Code CLI**: Installs the CLI (with retry logic), or uses a custom executable path
- **Standalone Action**: Published separately as `@anthropic-ai/claude-code-base-action` for direct use ### Phase 3: Execute
- **Inner Logic**: Used internally by this GitHub Action after preparation phase completes
Execution steps: Imports `base-action/` functions directly (no subprocess) to run Claude:
1. **MCP Server Setup**: Installs and configures GitHub MCP server for tool access 1. **Environment Setup**: Validates env vars, configures Claude Code settings, installs plugins
2. **Prompt Generation**: Creates context-rich prompts from GitHub data 2. **Prompt Preparation**: Writes the context-rich prompt to a temp file
3. **Claude Integration**: Executes via multiple providers (Anthropic API, AWS Bedrock, Google Vertex AI) 3. **Claude Integration**: Executes via multiple providers (Anthropic API, AWS Bedrock, Google Vertex AI)
4. **Result Processing**: Updates comments and creates branches/PRs as needed
The `base-action/` directory is also published separately as `@anthropic-ai/claude-code-base-action` for standalone use.
### Phase 4: Cleanup (always runs)
1. **Comment Update**: Updates the tracking comment with job link, branch, and status
2. **Step Summary**: Formats Claude's output into the GitHub Actions step summary
3. **SSH Signing Cleanup**: Removes SSH signing key (separate composite step, runs with `always()`)
4. **Token Revocation**: Revokes the GitHub App installation token (separate composite step, runs with `always()`)
### Key Architectural Components ### Key Architectural Components
@ -82,7 +89,7 @@ Execution steps:
``` ```
src/ src/
├── entrypoints/ # Action entry points ├── entrypoints/ # Action entry points
│ ├── prepare.ts # Main preparation logic │ ├── run.ts # Unified entrypoint (orchestrates all phases)
│ ├── update-comment-link.ts # Post-execution comment updates │ ├── update-comment-link.ts # Post-execution comment updates
│ └── format-turns.ts # Claude conversation formatting │ └── format-turns.ts # Claude conversation formatting
├── github/ # GitHub integration layer ├── github/ # GitHub integration layer

View File

@ -7,6 +7,7 @@
*/ */
import * as core from "@actions/core"; import * as core from "@actions/core";
import { dirname } from "path";
import { spawn } from "child_process"; import { spawn } from "child_process";
import { appendFile } from "fs/promises"; import { appendFile } from "fs/promises";
import { existsSync, readFileSync } from "fs"; import { existsSync, readFileSync } from "fs";
@ -37,10 +38,7 @@ async function installClaudeCode(): Promise<void> {
const customExecutable = process.env.PATH_TO_CLAUDE_CODE_EXECUTABLE; const customExecutable = process.env.PATH_TO_CLAUDE_CODE_EXECUTABLE;
if (customExecutable) { if (customExecutable) {
console.log(`Using custom Claude Code executable: ${customExecutable}`); console.log(`Using custom Claude Code executable: ${customExecutable}`);
const claudeDir = customExecutable.substring( const claudeDir = dirname(customExecutable);
0,
customExecutable.lastIndexOf("/"),
);
// Add to PATH by appending to GITHUB_PATH // Add to PATH by appending to GITHUB_PATH
const githubPath = process.env.GITHUB_PATH; const githubPath = process.env.GITHUB_PATH;
if (githubPath) { if (githubPath) {
@ -134,6 +132,8 @@ async function run() {
let prepareError: string | undefined; let prepareError: string | undefined;
let context: GitHubContext | undefined; let context: GitHubContext | undefined;
let octokit: Octokits | undefined; let octokit: Octokits | undefined;
// Track whether we've completed prepare phase, so we can attribute errors correctly
let prepareCompleted = false;
try { try {
// Phase 1: Prepare // Phase 1: Prepare
const actionInputsPresent = collectActionInputsPresence(); const actionInputsPresent = collectActionInputsPresence();
@ -195,6 +195,8 @@ async function run() {
commentId = prepareResult.commentId; commentId = prepareResult.commentId;
claudeBranch = prepareResult.branchInfo.claudeBranch; claudeBranch = prepareResult.branchInfo.claudeBranch;
baseBranch = prepareResult.branchInfo.baseBranch; baseBranch = prepareResult.branchInfo.baseBranch;
prepareCompleted = true;
// Set system prompt if available // Set system prompt if available
if (mode.getSystemPrompt) { if (mode.getSystemPrompt) {
const modeContext = mode.prepareContext(context, { const modeContext = mode.prepareContext(context, {
@ -260,8 +262,11 @@ async function run() {
core.setOutput("conclusion", claudeResult.conclusion); core.setOutput("conclusion", claudeResult.conclusion);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
prepareSuccess = false; // Only mark as prepare failure if we haven't completed the prepare phase
prepareError = errorMessage; if (!prepareCompleted) {
prepareSuccess = false;
prepareError = errorMessage;
}
core.setFailed(`Action failed with error: ${errorMessage}`); core.setFailed(`Action failed with error: ${errorMessage}`);
} finally { } finally {
// Phase 4: Cleanup (always runs) // Phase 4: Cleanup (always runs)