Compare commits

..

3 Commits

Author SHA1 Message Date
Ashwin Bhat
5dcc80706f add missing env var 2025-06-25 10:19:04 -07:00
tomoish
0704ffe815 resolve merge conflict in create-prompt.test.ts 2025-06-24 23:21:29 +09:00
tomoish
90b0a15006 Add label trigger functionality to Claude Code Action
- introduced a new input parameter `label_trigger` in `action.yml` to allow triggering actions based on specific labels applied to issues.
- Enhanced the context preparation and event handling in the code to support the new labled event.
2025-06-17 00:32:12 +09:00
10 changed files with 13 additions and 126 deletions

4
FAQ.md
View File

@@ -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:

View File

@@ -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)

View File

@@ -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 != ''

View 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",
},
};

View File

@@ -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

View File

@@ -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,

View File

@@ -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,
};
}),
);

View File

@@ -19,8 +19,6 @@ const defaultInputs = {
useBedrock: false,
useVertex: false,
timeoutMinutes: 30,
branchPrefix: "claude/",
useStickyComment: false,
};
const defaultRepository = {

View File

@@ -67,8 +67,6 @@ describe("checkWritePermissions", () => {
disallowedTools: [],
customInstructions: "",
directPrompt: "",
branchPrefix: "claude/",
useStickyComment: false,
},
});

View File

@@ -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);