mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-24 15:34:13 +08:00
Compare commits
3 Commits
v0.0.27
...
feat-add-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dcc80706f | ||
|
|
0704ffe815 | ||
|
|
90b0a15006 |
4
FAQ.md
4
FAQ.md
@@ -12,10 +12,6 @@ The `github-actions` user cannot trigger subsequent GitHub Actions workflows. Th
|
||||
|
||||
Only users with **write permissions** to the repository can trigger Claude. This is a security feature to prevent unauthorized use. Make sure the user commenting has at least write access to the repository.
|
||||
|
||||
### Why can't I assign @claude to an issue on my repository?
|
||||
|
||||
If you're in a public repository, you should be able to assign to Claude without issue. If it's a private organization repository, you can only assign to users in your own organization, which Claude isn't. In this case, you'll need to make a custom user in that case.
|
||||
|
||||
### Why am I getting OIDC authentication errors?
|
||||
|
||||
If you're using the default GitHub App authentication, you must add the `id-token: write` permission to your workflow:
|
||||
|
||||
@@ -82,10 +82,8 @@ jobs:
|
||||
| --------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
|
||||
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - |
|
||||
| `direct_prompt` | Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows) | No | - |
|
||||
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | No | - |
|
||||
| `max_turns` | Maximum number of conversation turns Claude can take (limits back-and-forth exchanges) | No | - |
|
||||
| `timeout_minutes` | Timeout in minutes for execution | No | `30` |
|
||||
| `use_sticky_comment` | Use just one comment to deliver PR comments (only applies for pull_request event workflows) | No | `false` |
|
||||
| `github_token` | GitHub token for Claude to operate with. **Only include this if you're connecting a custom GitHub app of your own!** | No | - |
|
||||
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
|
||||
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
|
||||
@@ -98,7 +96,6 @@ jobs:
|
||||
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - |
|
||||
| `label_trigger` | The label name that triggers the action when applied to an issue (e.g. "claude") | No | - |
|
||||
| `trigger_phrase` | The trigger phrase to look for in comments, issue/PR bodies, and issue titles | No | `@claude` |
|
||||
| `branch_prefix` | The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format) | No | `claude/` |
|
||||
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML format) | No | "" |
|
||||
|
||||
\*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)
|
||||
|
||||
15
action.yml
15
action.yml
@@ -19,10 +19,6 @@ inputs:
|
||||
base_branch:
|
||||
description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)"
|
||||
required: false
|
||||
branch_prefix:
|
||||
description: "The prefix to use for Claude branches (defaults to 'claude/', use 'claude-' for dash format)"
|
||||
required: false
|
||||
default: "claude/"
|
||||
|
||||
# Claude Code configuration
|
||||
model:
|
||||
@@ -78,10 +74,6 @@ inputs:
|
||||
description: "Timeout in minutes for execution"
|
||||
required: false
|
||||
default: "30"
|
||||
use_sticky_comment:
|
||||
description: "Use just one comment to deliver issue/PR comments"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
outputs:
|
||||
execution_file:
|
||||
@@ -109,10 +101,9 @@ runs:
|
||||
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/prepare.ts
|
||||
env:
|
||||
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
|
||||
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
||||
LABEL_TRIGGER: ${{ inputs.label_trigger }}
|
||||
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
||||
BASE_BRANCH: ${{ inputs.base_branch }}
|
||||
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
|
||||
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
|
||||
DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }}
|
||||
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
|
||||
@@ -120,12 +111,11 @@ runs:
|
||||
MCP_CONFIG: ${{ inputs.mcp_config }}
|
||||
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||
uses: anthropics/claude-code-base-action@bdaad5f64e7ad7a8c0be290a3c49d0fa7e1bb442 # v0.0.29
|
||||
uses: anthropics/claude-code-base-action@ce5cfd683932f58cb459e749f20b06d2fb30c265 # v0.0.25
|
||||
with:
|
||||
prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||
@@ -185,7 +175,6 @@ runs:
|
||||
TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }}
|
||||
PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
|
||||
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
||||
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||
|
||||
- name: Display Claude Code Report
|
||||
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
||||
|
||||
@@ -35,8 +35,6 @@ export type ParsedGitHubContext = {
|
||||
customInstructions: string;
|
||||
directPrompt: string;
|
||||
baseBranch?: string;
|
||||
branchPrefix: string;
|
||||
useStickyComment: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -62,8 +60,6 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
||||
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
|
||||
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
||||
baseBranch: process.env.BASE_BRANCH,
|
||||
branchPrefix: process.env.BRANCH_PREFIX ?? "claude/",
|
||||
useStickyComment: process.env.STICKY_COMMENT === "true",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function setupBranch(
|
||||
): Promise<BranchInfo> {
|
||||
const { owner, repo } = context.repository;
|
||||
const entityNumber = context.entityNumber;
|
||||
const { baseBranch, branchPrefix } = context.inputs;
|
||||
const { baseBranch } = context.inputs;
|
||||
const isPR = context.isPR;
|
||||
|
||||
if (isPR) {
|
||||
@@ -97,7 +97,7 @@ export async function setupBranch(
|
||||
.split("T")
|
||||
.join("_");
|
||||
|
||||
const newBranch = `${branchPrefix}${entityType}-${entityNumber}-${timestamp}`;
|
||||
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
|
||||
|
||||
try {
|
||||
// Get the SHA of the source branch
|
||||
|
||||
@@ -9,7 +9,6 @@ import { appendFileSync } from "fs";
|
||||
import { createJobRunLink, createCommentBody } from "./common";
|
||||
import {
|
||||
isPullRequestReviewCommentEvent,
|
||||
isPullRequestEvent,
|
||||
type ParsedGitHubContext,
|
||||
} from "../../context";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
@@ -26,39 +25,8 @@ export async function createInitialComment(
|
||||
try {
|
||||
let response;
|
||||
|
||||
if (
|
||||
context.inputs.useStickyComment &&
|
||||
context.isPR &&
|
||||
!isPullRequestEvent(context)
|
||||
) {
|
||||
const comments = await octokit.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: context.entityNumber,
|
||||
});
|
||||
const existingComment = comments.data.find(
|
||||
(comment) =>
|
||||
comment.user?.login.indexOf("claude[bot]") !== -1 ||
|
||||
comment.body === initialBody,
|
||||
);
|
||||
if (existingComment) {
|
||||
response = await octokit.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existingComment.id,
|
||||
body: initialBody,
|
||||
});
|
||||
} else {
|
||||
// Create new comment if no existing one found
|
||||
response = await octokit.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: context.entityNumber,
|
||||
body: initialBody,
|
||||
});
|
||||
}
|
||||
} else if (isPullRequestReviewCommentEvent(context)) {
|
||||
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
||||
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
||||
if (isPullRequestReviewCommentEvent(context)) {
|
||||
response = await octokit.rest.pulls.createReplyForReviewComment({
|
||||
owner,
|
||||
repo,
|
||||
|
||||
@@ -125,58 +125,13 @@ server.tool(
|
||||
? filePath
|
||||
: join(REPO_DIR, filePath);
|
||||
|
||||
// Check if file is binary (images, etc.)
|
||||
const isBinaryFile =
|
||||
/\.(png|jpg|jpeg|gif|webp|ico|pdf|zip|tar|gz|exe|bin|woff|woff2|ttf|eot)$/i.test(
|
||||
filePath,
|
||||
);
|
||||
|
||||
if (isBinaryFile) {
|
||||
// For binary files, create a blob first using the Blobs API
|
||||
const binaryContent = await readFile(fullPath);
|
||||
|
||||
// Create blob using Blobs API (supports encoding parameter)
|
||||
const blobUrl = `${GITHUB_API_URL}/repos/${owner}/${repo}/git/blobs`;
|
||||
const blobResponse = await fetch(blobUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${githubToken}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: binaryContent.toString("base64"),
|
||||
encoding: "base64",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!blobResponse.ok) {
|
||||
const errorText = await blobResponse.text();
|
||||
throw new Error(
|
||||
`Failed to create blob for ${filePath}: ${blobResponse.status} - ${errorText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const blobData = (await blobResponse.json()) as { sha: string };
|
||||
|
||||
// Return tree entry with blob SHA
|
||||
return {
|
||||
path: filePath,
|
||||
mode: "100644",
|
||||
type: "blob",
|
||||
sha: blobData.sha,
|
||||
};
|
||||
} else {
|
||||
// For text files, include content directly in tree
|
||||
const content = await readFile(fullPath, "utf-8");
|
||||
return {
|
||||
path: filePath,
|
||||
mode: "100644",
|
||||
type: "blob",
|
||||
content: content,
|
||||
};
|
||||
}
|
||||
const content = await readFile(fullPath, "utf-8");
|
||||
return {
|
||||
path: filePath,
|
||||
mode: "100644",
|
||||
type: "blob",
|
||||
content: content,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ const defaultInputs = {
|
||||
useBedrock: false,
|
||||
useVertex: false,
|
||||
timeoutMinutes: 30,
|
||||
branchPrefix: "claude/",
|
||||
useStickyComment: false,
|
||||
};
|
||||
|
||||
const defaultRepository = {
|
||||
|
||||
@@ -67,8 +67,6 @@ describe("checkWritePermissions", () => {
|
||||
disallowedTools: [],
|
||||
customInstructions: "",
|
||||
directPrompt: "",
|
||||
branchPrefix: "claude/",
|
||||
useStickyComment: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -35,8 +35,6 @@ describe("checkContainsTrigger", () => {
|
||||
allowedTools: [],
|
||||
disallowedTools: [],
|
||||
customInstructions: "",
|
||||
branchPrefix: "claude/",
|
||||
useStickyComment: false,
|
||||
},
|
||||
});
|
||||
expect(checkContainsTrigger(context)).toBe(true);
|
||||
@@ -64,8 +62,6 @@ describe("checkContainsTrigger", () => {
|
||||
allowedTools: [],
|
||||
disallowedTools: [],
|
||||
customInstructions: "",
|
||||
branchPrefix: "claude/",
|
||||
useStickyComment: false,
|
||||
},
|
||||
});
|
||||
expect(checkContainsTrigger(context)).toBe(false);
|
||||
@@ -277,8 +273,6 @@ describe("checkContainsTrigger", () => {
|
||||
allowedTools: [],
|
||||
disallowedTools: [],
|
||||
customInstructions: "",
|
||||
branchPrefix: "claude/",
|
||||
useStickyComment: false,
|
||||
},
|
||||
});
|
||||
expect(checkContainsTrigger(context)).toBe(true);
|
||||
@@ -307,8 +301,6 @@ describe("checkContainsTrigger", () => {
|
||||
allowedTools: [],
|
||||
disallowedTools: [],
|
||||
customInstructions: "",
|
||||
branchPrefix: "claude/",
|
||||
useStickyComment: false,
|
||||
},
|
||||
});
|
||||
expect(checkContainsTrigger(context)).toBe(true);
|
||||
@@ -337,8 +329,6 @@ describe("checkContainsTrigger", () => {
|
||||
allowedTools: [],
|
||||
disallowedTools: [],
|
||||
customInstructions: "",
|
||||
branchPrefix: "claude/",
|
||||
useStickyComment: false,
|
||||
},
|
||||
});
|
||||
expect(checkContainsTrigger(context)).toBe(false);
|
||||
|
||||
Reference in New Issue
Block a user