mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 23:14:13 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00f9595fb4 | ||
|
|
79f2086fce | ||
|
|
bcb072b63f | ||
|
|
e3b3e531a7 | ||
|
|
a7665d3698 | ||
|
|
91c510a769 | ||
|
|
1e006bf2d0 |
4
FAQ.md
4
FAQ.md
@@ -12,6 +12,10 @@ 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.
|
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?
|
### 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:
|
If you're using the default GitHub App authentication, you must add the `id-token: write` permission to your workflow:
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ jobs:
|
|||||||
| `base_branch` | The base branch to use for creating new branches (e.g., 'main', 'develop') | 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 | - |
|
| `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` |
|
| `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 | - |
|
| `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 | - |
|
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | - |
|
||||||
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
|
| `anthropic_model` | **DEPRECATED**: Use `model` instead. Kept for backward compatibility. | No | - |
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ inputs:
|
|||||||
description: "Timeout in minutes for execution"
|
description: "Timeout in minutes for execution"
|
||||||
required: false
|
required: false
|
||||||
default: "30"
|
default: "30"
|
||||||
|
use_sticky_comment:
|
||||||
|
description: "Use just one comment to deliver issue/PR comments"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
execution_file:
|
execution_file:
|
||||||
@@ -106,6 +110,7 @@ runs:
|
|||||||
env:
|
env:
|
||||||
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
|
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
|
||||||
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
|
||||||
|
LABEL_TRIGGER: ${{ inputs.label_trigger }}
|
||||||
BASE_BRANCH: ${{ inputs.base_branch }}
|
BASE_BRANCH: ${{ inputs.base_branch }}
|
||||||
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
|
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
|
||||||
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
|
ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
|
||||||
@@ -115,11 +120,12 @@ runs:
|
|||||||
MCP_CONFIG: ${{ inputs.mcp_config }}
|
MCP_CONFIG: ${{ inputs.mcp_config }}
|
||||||
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
|
|
||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
id: claude-code
|
id: claude-code
|
||||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||||
uses: anthropics/claude-code-base-action@ba0557c14198bf2fbafbfe80932dde39e574a14c # v0.0.26
|
uses: anthropics/claude-code-base-action@bdaad5f64e7ad7a8c0be290a3c49d0fa7e1bb442 # v0.0.29
|
||||||
with:
|
with:
|
||||||
prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
prompt_file: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
|
||||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||||
@@ -179,6 +185,7 @@ 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 || '' }}
|
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_SUCCESS: ${{ steps.prepare.outcome == 'success' }}
|
||||||
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }}
|
||||||
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
|
|
||||||
- name: Display Claude Code Report
|
- name: Display Claude Code Report
|
||||||
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type ParsedGitHubContext = {
|
|||||||
directPrompt: string;
|
directPrompt: string;
|
||||||
baseBranch?: string;
|
baseBranch?: string;
|
||||||
branchPrefix: string;
|
branchPrefix: string;
|
||||||
|
useStickyComment: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
|||||||
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
directPrompt: process.env.DIRECT_PROMPT ?? "",
|
||||||
baseBranch: process.env.BASE_BRANCH,
|
baseBranch: process.env.BASE_BRANCH,
|
||||||
branchPrefix: process.env.BRANCH_PREFIX ?? "claude/",
|
branchPrefix: process.env.BRANCH_PREFIX ?? "claude/",
|
||||||
|
useStickyComment: process.env.STICKY_COMMENT === "true",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { appendFileSync } from "fs";
|
|||||||
import { createJobRunLink, createCommentBody } from "./common";
|
import { createJobRunLink, createCommentBody } from "./common";
|
||||||
import {
|
import {
|
||||||
isPullRequestReviewCommentEvent,
|
isPullRequestReviewCommentEvent,
|
||||||
|
isPullRequestEvent,
|
||||||
type ParsedGitHubContext,
|
type ParsedGitHubContext,
|
||||||
} from "../../context";
|
} from "../../context";
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { Octokit } from "@octokit/rest";
|
||||||
@@ -25,8 +26,39 @@ export async function createInitialComment(
|
|||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
if (
|
||||||
if (isPullRequestReviewCommentEvent(context)) {
|
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
|
||||||
response = await octokit.rest.pulls.createReplyForReviewComment({
|
response = await octokit.rest.pulls.createReplyForReviewComment({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
|
|||||||
@@ -125,13 +125,58 @@ server.tool(
|
|||||||
? filePath
|
? filePath
|
||||||
: join(REPO_DIR, filePath);
|
: join(REPO_DIR, filePath);
|
||||||
|
|
||||||
const content = await readFile(fullPath, "utf-8");
|
// Check if file is binary (images, etc.)
|
||||||
return {
|
const isBinaryFile =
|
||||||
path: filePath,
|
/\.(png|jpg|jpeg|gif|webp|ico|pdf|zip|tar|gz|exe|bin|woff|woff2|ttf|eot)$/i.test(
|
||||||
mode: "100644",
|
filePath,
|
||||||
type: "blob",
|
);
|
||||||
content: content,
|
|
||||||
};
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const defaultInputs = {
|
|||||||
useVertex: false,
|
useVertex: false,
|
||||||
timeoutMinutes: 30,
|
timeoutMinutes: 30,
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRepository = {
|
const defaultRepository = {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ describe("checkWritePermissions", () => {
|
|||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
directPrompt: "",
|
directPrompt: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(true);
|
expect(checkContainsTrigger(context)).toBe(true);
|
||||||
@@ -64,6 +65,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(false);
|
expect(checkContainsTrigger(context)).toBe(false);
|
||||||
@@ -276,6 +278,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(true);
|
expect(checkContainsTrigger(context)).toBe(true);
|
||||||
@@ -305,6 +308,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(true);
|
expect(checkContainsTrigger(context)).toBe(true);
|
||||||
@@ -334,6 +338,7 @@ describe("checkContainsTrigger", () => {
|
|||||||
disallowedTools: [],
|
disallowedTools: [],
|
||||||
customInstructions: "",
|
customInstructions: "",
|
||||||
branchPrefix: "claude/",
|
branchPrefix: "claude/",
|
||||||
|
useStickyComment: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(checkContainsTrigger(context)).toBe(false);
|
expect(checkContainsTrigger(context)).toBe(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user