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
9 changed files with 11 additions and 76 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. 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:

View File

@@ -82,7 +82,6 @@ jobs:
| --------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- | | --------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
| `anthropic_api_key` | Anthropic API key (required for direct API, not needed for Bedrock/Vertex) | No\* | - | | `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 | - | | `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 | - | | `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` |
| `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 | - |
@@ -97,7 +96,6 @@ jobs:
| `assignee_trigger` | The assignee username that triggers the action (e.g. @claude). Only used for issue assignment | No | - | | `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 | - | | `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` | | `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 | "" | | `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) \*Required when using direct Anthropic API (default and when not using Bedrock or Vertex)

View File

@@ -19,10 +19,6 @@ inputs:
base_branch: base_branch:
description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)" description: "The branch to use as the base/source when creating new branches (defaults to repository default branch)"
required: false 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 # Claude Code configuration
model: model:
@@ -105,10 +101,9 @@ runs:
bun run ${GITHUB_ACTION_PATH}/src/entrypoints/prepare.ts bun run ${GITHUB_ACTION_PATH}/src/entrypoints/prepare.ts
env: env:
TRIGGER_PHRASE: ${{ inputs.trigger_phrase }} TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
LABEL_TRIGGER: ${{ inputs.label_trigger }} LABEL_TRIGGER: ${{ inputs.label_trigger }}
ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
BASE_BRANCH: ${{ inputs.base_branch }} BASE_BRANCH: ${{ inputs.base_branch }}
BRANCH_PREFIX: ${{ inputs.branch_prefix }}
ALLOWED_TOOLS: ${{ inputs.allowed_tools }} ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }} DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }}
CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }} CUSTOM_INSTRUCTIONS: ${{ inputs.custom_instructions }}
@@ -120,7 +115,7 @@ runs:
- 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@f6ef8c1000c0197b625af70349f68cb212e34fc1 # v0.0.28 uses: anthropics/claude-code-base-action@ce5cfd683932f58cb459e749f20b06d2fb30c265 # v0.0.25
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 }}

View File

@@ -35,7 +35,6 @@ export type ParsedGitHubContext = {
customInstructions: string; customInstructions: string;
directPrompt: string; directPrompt: string;
baseBranch?: string; baseBranch?: string;
branchPrefix: string;
}; };
}; };
@@ -61,7 +60,6 @@ export function parseGitHubContext(): ParsedGitHubContext {
customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "", customInstructions: process.env.CUSTOM_INSTRUCTIONS ?? "",
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/",
}, },
}; };

View File

@@ -26,7 +26,7 @@ export async function setupBranch(
): Promise<BranchInfo> { ): Promise<BranchInfo> {
const { owner, repo } = context.repository; const { owner, repo } = context.repository;
const entityNumber = context.entityNumber; const entityNumber = context.entityNumber;
const { baseBranch, branchPrefix } = context.inputs; const { baseBranch } = context.inputs;
const isPR = context.isPR; const isPR = context.isPR;
if (isPR) { if (isPR) {
@@ -97,7 +97,7 @@ export async function setupBranch(
.split("T") .split("T")
.join("_"); .join("_");
const newBranch = `${branchPrefix}${entityType}-${entityNumber}-${timestamp}`; const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
try { try {
// Get the SHA of the source branch // Get the SHA of the source branch

View File

@@ -125,58 +125,13 @@ server.tool(
? filePath ? filePath
: join(REPO_DIR, filePath); : join(REPO_DIR, filePath);
// Check if file is binary (images, etc.) const content = await readFile(fullPath, "utf-8");
const isBinaryFile = return {
/\.(png|jpg|jpeg|gif|webp|ico|pdf|zip|tar|gz|exe|bin|woff|woff2|ttf|eot)$/i.test( path: filePath,
filePath, mode: "100644",
); 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,
};
}
}), }),
); );

View File

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

View File

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

View File

@@ -35,7 +35,6 @@ describe("checkContainsTrigger", () => {
allowedTools: [], allowedTools: [],
disallowedTools: [], disallowedTools: [],
customInstructions: "", customInstructions: "",
branchPrefix: "claude/",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(true); expect(checkContainsTrigger(context)).toBe(true);
@@ -63,7 +62,6 @@ describe("checkContainsTrigger", () => {
allowedTools: [], allowedTools: [],
disallowedTools: [], disallowedTools: [],
customInstructions: "", customInstructions: "",
branchPrefix: "claude/",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(false); expect(checkContainsTrigger(context)).toBe(false);
@@ -275,7 +273,6 @@ describe("checkContainsTrigger", () => {
allowedTools: [], allowedTools: [],
disallowedTools: [], disallowedTools: [],
customInstructions: "", customInstructions: "",
branchPrefix: "claude/",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(true); expect(checkContainsTrigger(context)).toBe(true);
@@ -304,7 +301,6 @@ describe("checkContainsTrigger", () => {
allowedTools: [], allowedTools: [],
disallowedTools: [], disallowedTools: [],
customInstructions: "", customInstructions: "",
branchPrefix: "claude/",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(true); expect(checkContainsTrigger(context)).toBe(true);
@@ -333,7 +329,6 @@ describe("checkContainsTrigger", () => {
allowedTools: [], allowedTools: [],
disallowedTools: [], disallowedTools: [],
customInstructions: "", customInstructions: "",
branchPrefix: "claude/",
}, },
}); });
expect(checkContainsTrigger(context)).toBe(false); expect(checkContainsTrigger(context)).toBe(false);