mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
Merge branch 'main' of https://github.com/anthropics/claude-code-action into v1-dev
This commit is contained in:
@@ -750,7 +750,7 @@ export async function createPrompt(
|
||||
modeContext.claudeBranch,
|
||||
);
|
||||
|
||||
await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, {
|
||||
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
@@ -769,7 +769,7 @@ export async function createPrompt(
|
||||
|
||||
// Write the prompt file
|
||||
await writeFile(
|
||||
`${process.env.RUNNER_TEMP}/claude-prompts/claude-prompt.txt`,
|
||||
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
|
||||
promptContent,
|
||||
);
|
||||
|
||||
|
||||
59
src/entrypoints/collect-inputs.ts
Normal file
59
src/entrypoints/collect-inputs.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
export function collectActionInputsPresence(): void {
|
||||
const inputDefaults: Record<string, string> = {
|
||||
trigger_phrase: "@claude",
|
||||
assignee_trigger: "",
|
||||
label_trigger: "claude",
|
||||
base_branch: "",
|
||||
branch_prefix: "claude/",
|
||||
allowed_bots: "",
|
||||
mode: "tag",
|
||||
model: "",
|
||||
anthropic_model: "",
|
||||
fallback_model: "",
|
||||
allowed_tools: "",
|
||||
disallowed_tools: "",
|
||||
custom_instructions: "",
|
||||
direct_prompt: "",
|
||||
override_prompt: "",
|
||||
mcp_config: "",
|
||||
additional_permissions: "",
|
||||
claude_env: "",
|
||||
settings: "",
|
||||
anthropic_api_key: "",
|
||||
claude_code_oauth_token: "",
|
||||
github_token: "",
|
||||
max_turns: "",
|
||||
use_sticky_comment: "false",
|
||||
use_commit_signing: "false",
|
||||
experimental_allowed_domains: "",
|
||||
};
|
||||
|
||||
const allInputsJson = process.env.ALL_INPUTS;
|
||||
if (!allInputsJson) {
|
||||
console.log("ALL_INPUTS environment variable not found");
|
||||
core.setOutput("action_inputs_present", JSON.stringify({}));
|
||||
return;
|
||||
}
|
||||
|
||||
let allInputs: Record<string, string>;
|
||||
try {
|
||||
allInputs = JSON.parse(allInputsJson);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse ALL_INPUTS JSON:", e);
|
||||
core.setOutput("action_inputs_present", JSON.stringify({}));
|
||||
return;
|
||||
}
|
||||
|
||||
const presentInputs: Record<string, boolean> = {};
|
||||
|
||||
for (const [name, defaultValue] of Object.entries(inputDefaults)) {
|
||||
const actualValue = allInputs[name] || "";
|
||||
|
||||
const isSet = actualValue !== defaultValue;
|
||||
presentInputs[name] = isSet;
|
||||
}
|
||||
|
||||
core.setOutput("action_inputs_present", JSON.stringify(presentInputs));
|
||||
}
|
||||
@@ -12,9 +12,12 @@ import { createOctokit } from "../github/api/client";
|
||||
import { parseGitHubContext, isEntityContext } from "../github/context";
|
||||
import { getMode } from "../modes/registry";
|
||||
import { prepare } from "../prepare";
|
||||
import { collectActionInputsPresence } from "./collect-inputs";
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
collectActionInputsPresence();
|
||||
|
||||
// Parse GitHub context first to enable mode detection
|
||||
const context = parseGitHubContext();
|
||||
|
||||
|
||||
@@ -31,8 +31,30 @@ async function exchangeForAppToken(oidcToken: string): Promise<string> {
|
||||
const responseJson = (await response.json()) as {
|
||||
error?: {
|
||||
message?: string;
|
||||
details?: {
|
||||
error_code?: string;
|
||||
};
|
||||
};
|
||||
type?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
// Check for specific workflow validation error codes that should skip the action
|
||||
const errorCode = responseJson.error?.details?.error_code;
|
||||
|
||||
if (errorCode === "workflow_not_found_on_default_branch") {
|
||||
const message =
|
||||
responseJson.message ??
|
||||
responseJson.error?.message ??
|
||||
"Workflow validation failed";
|
||||
core.warning(`Skipping action due to workflow validation: ${message}`);
|
||||
console.log(
|
||||
"Action skipped due to workflow validation error. This is expected when adding Claude Code workflows to new repositories or on PRs with workflow changes. If you're seeing this, your workflow will begin working once you merge your PR.",
|
||||
);
|
||||
core.setOutput("skipped_due_to_workflow_validation_mismatch", "true");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error(
|
||||
`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson?.error?.message ?? "Unknown error"}`,
|
||||
);
|
||||
@@ -77,8 +99,9 @@ export async function setupGitHubToken(): Promise<string> {
|
||||
core.setOutput("GITHUB_TOKEN", appToken);
|
||||
return appToken;
|
||||
} catch (error) {
|
||||
// Only set failed if we get here - workflow validation errors will exit(0) before this
|
||||
core.setFailed(
|
||||
`Failed to setup GitHub token: ${error}.\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`,
|
||||
`Failed to setup GitHub token: ${error}\n\nIf you instead wish to use this action with a custom GitHub token or custom GitHub app, provide a \`github_token\` in the \`uses\` section of the app in your workflow yml file.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,41 @@ export function sanitizeContent(content: string): string {
|
||||
content = stripMarkdownLinkTitles(content);
|
||||
content = stripHiddenAttributes(content);
|
||||
content = normalizeHtmlEntities(content);
|
||||
content = redactGitHubTokens(content);
|
||||
return content;
|
||||
}
|
||||
|
||||
export function redactGitHubTokens(content: string): string {
|
||||
// GitHub Personal Access Tokens (classic): ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars)
|
||||
content = content.replace(
|
||||
/\bghp_[A-Za-z0-9]{36}\b/g,
|
||||
"[REDACTED_GITHUB_TOKEN]",
|
||||
);
|
||||
|
||||
// GitHub OAuth tokens: gho_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars)
|
||||
content = content.replace(
|
||||
/\bgho_[A-Za-z0-9]{36}\b/g,
|
||||
"[REDACTED_GITHUB_TOKEN]",
|
||||
);
|
||||
|
||||
// GitHub installation tokens: ghs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars)
|
||||
content = content.replace(
|
||||
/\bghs_[A-Za-z0-9]{36}\b/g,
|
||||
"[REDACTED_GITHUB_TOKEN]",
|
||||
);
|
||||
|
||||
// GitHub refresh tokens: ghr_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars)
|
||||
content = content.replace(
|
||||
/\bghr_[A-Za-z0-9]{36}\b/g,
|
||||
"[REDACTED_GITHUB_TOKEN]",
|
||||
);
|
||||
|
||||
// GitHub fine-grained personal access tokens: github_pat_XXXXXXXXXX (up to 255 chars)
|
||||
content = content.replace(
|
||||
/\bgithub_pat_[A-Za-z0-9_]{11,221}\b/g,
|
||||
"[REDACTED_GITHUB_TOKEN]",
|
||||
);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { z } from "zod";
|
||||
import { GITHUB_API_URL } from "../github/api/config";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
|
||||
import { sanitizeContent } from "../github/utils/sanitizer";
|
||||
|
||||
// Get repository information from environment variables
|
||||
const REPO_OWNER = process.env.REPO_OWNER;
|
||||
@@ -54,11 +55,13 @@ server.tool(
|
||||
const isPullRequestReviewComment =
|
||||
eventName === "pull_request_review_comment";
|
||||
|
||||
const sanitizedBody = sanitizeContent(body);
|
||||
|
||||
const result = await updateClaudeComment(octokit, {
|
||||
owner,
|
||||
repo,
|
||||
commentId,
|
||||
body,
|
||||
body: sanitizedBody,
|
||||
isPullRequestReviewComment,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
import { createOctokit } from "../github/api/client";
|
||||
import { sanitizeContent } from "../github/utils/sanitizer";
|
||||
|
||||
// Get repository and PR information from environment variables
|
||||
const REPO_OWNER = process.env.REPO_OWNER;
|
||||
@@ -81,6 +82,9 @@ server.tool(
|
||||
|
||||
const octokit = createOctokit(githubToken).rest;
|
||||
|
||||
// Sanitize the comment body to remove any potential GitHub tokens
|
||||
const sanitizedBody = sanitizeContent(body);
|
||||
|
||||
// Validate that either line or both startLine and line are provided
|
||||
if (!line && !startLine) {
|
||||
throw new Error(
|
||||
@@ -104,7 +108,7 @@ server.tool(
|
||||
owner,
|
||||
repo,
|
||||
pull_number,
|
||||
body,
|
||||
body: sanitizedBody,
|
||||
path,
|
||||
side: side || "RIGHT",
|
||||
commit_id: commit_id || pr.data.head.sha,
|
||||
|
||||
@@ -43,7 +43,7 @@ export const agentMode: Mode = {
|
||||
|
||||
async prepare({ context, githubToken }: ModeOptions): Promise<ModeResult> {
|
||||
// Create prompt directory
|
||||
await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, {
|
||||
await mkdir(`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts`, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ export const agentMode: Mode = {
|
||||
`Repository: ${context.repository.owner}/${context.repository.repo}`;
|
||||
|
||||
await writeFile(
|
||||
`${process.env.RUNNER_TEMP}/claude-prompts/claude-prompt.txt`,
|
||||
`${process.env.RUNNER_TEMP || "/tmp"}/claude-prompts/claude-prompt.txt`,
|
||||
promptContent,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user