mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 15:04:13 +08:00
Compare commits
5 Commits
claude/iss
...
ashwin/err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44e276ae4f | ||
|
|
baa1ecb265 | ||
|
|
81181ca658 | ||
|
|
176dbc369d | ||
|
|
8ae72a97c6 |
@@ -446,7 +446,7 @@ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
```
|
||||
|
||||
This applies to all sensitive values including API keys, access tokens, and credentials.
|
||||
We also reccomend that you always use short-lived tokens when possible
|
||||
We also recommend that you always use short-lived tokens when possible
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ runs:
|
||||
- name: Run Claude Code
|
||||
id: claude-code
|
||||
if: steps.prepare.outputs.contains_trigger == 'true'
|
||||
uses: anthropics/claude-code-base-action@5097b6cdfe5fc5a3ac0166cc344c34ed23c93982 # https://github.com/anthropics/claude-code-base-action/releases/tag/v0.0.5
|
||||
uses: anthropics/claude-code-base-action@266585c92dd90d61d3806a3367582c4f6224e892 # https://github.com/anthropics/claude-code-base-action/releases/tag/v0.0.6
|
||||
with:
|
||||
prompt_file: /tmp/claude-prompts/claude-prompt.txt
|
||||
allowed_tools: ${{ env.ALLOWED_TOOLS }}
|
||||
@@ -147,6 +147,8 @@ runs:
|
||||
CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }}
|
||||
OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }}
|
||||
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 || '' }}
|
||||
|
||||
- name: Display Claude Code Report
|
||||
if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != ''
|
||||
|
||||
@@ -58,27 +58,10 @@ export function buildAllowedToolsString(
|
||||
|
||||
export function buildDisallowedToolsString(
|
||||
customDisallowedTools?: string,
|
||||
allowedTools?: string,
|
||||
): string {
|
||||
let disallowedTools = [...DISALLOWED_TOOLS];
|
||||
|
||||
// If user has explicitly allowed some hardcoded disallowed tools, remove them from disallowed list
|
||||
if (allowedTools) {
|
||||
const allowedToolsArray = allowedTools
|
||||
.split(",")
|
||||
.map((tool) => tool.trim());
|
||||
disallowedTools = disallowedTools.filter(
|
||||
(tool) => !allowedToolsArray.includes(tool),
|
||||
);
|
||||
}
|
||||
|
||||
let allDisallowedTools = disallowedTools.join(",");
|
||||
let allDisallowedTools = DISALLOWED_TOOLS.join(",");
|
||||
if (customDisallowedTools) {
|
||||
if (allDisallowedTools) {
|
||||
allDisallowedTools = `${allDisallowedTools},${customDisallowedTools}`;
|
||||
} else {
|
||||
allDisallowedTools = customDisallowedTools;
|
||||
}
|
||||
}
|
||||
return allDisallowedTools;
|
||||
}
|
||||
@@ -665,7 +648,6 @@ export async function createPrompt(
|
||||
);
|
||||
const allDisallowedTools = buildDisallowedToolsString(
|
||||
preparedContext.disallowedTools,
|
||||
preparedContext.allowedTools,
|
||||
);
|
||||
|
||||
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);
|
||||
|
||||
@@ -92,7 +92,10 @@ async function run() {
|
||||
);
|
||||
core.setOutput("mcp_config", mcpConfig);
|
||||
} catch (error) {
|
||||
core.setFailed(`Prepare step failed with error: ${error}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
core.setFailed(`Prepare step failed with error: ${errorMessage}`);
|
||||
// Also output the clean error message for the action to capture
|
||||
core.setOutput("prepare_error", errorMessage);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,16 @@ async function run() {
|
||||
duration_api_ms?: number;
|
||||
} | null = null;
|
||||
let actionFailed = false;
|
||||
let errorDetails: string | undefined;
|
||||
|
||||
// First check if prepare step failed
|
||||
const prepareSuccess = process.env.PREPARE_SUCCESS !== "false";
|
||||
const prepareError = process.env.PREPARE_ERROR;
|
||||
|
||||
if (!prepareSuccess && prepareError) {
|
||||
actionFailed = true;
|
||||
errorDetails = prepareError;
|
||||
} else {
|
||||
// Check for existence of output file and parse it if available
|
||||
try {
|
||||
const outputFile = process.env.OUTPUT_FILE;
|
||||
@@ -170,7 +179,7 @@ async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the action failed by looking at the exit code or error marker
|
||||
// Check if the Claude action failed
|
||||
const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false";
|
||||
actionFailed = !claudeSuccess;
|
||||
} catch (error) {
|
||||
@@ -178,6 +187,7 @@ async function run() {
|
||||
// If we can't read the file, check for any failure markers
|
||||
actionFailed = process.env.CLAUDE_SUCCESS === "false";
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare input for updateCommentBody function
|
||||
const commentInput: CommentUpdateInput = {
|
||||
@@ -189,6 +199,7 @@ async function run() {
|
||||
prLink,
|
||||
branchName: shouldDeleteBranch ? undefined : claudeBranch,
|
||||
triggerUsername,
|
||||
errorDetails,
|
||||
};
|
||||
|
||||
const updatedBody = updateCommentBody(commentInput);
|
||||
|
||||
@@ -15,6 +15,7 @@ export type CommentUpdateInput = {
|
||||
prLink?: string;
|
||||
branchName?: string;
|
||||
triggerUsername?: string;
|
||||
errorDetails?: string;
|
||||
};
|
||||
|
||||
export function ensureProperlyEncodedUrl(url: string): string | null {
|
||||
@@ -75,6 +76,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
||||
actionFailed,
|
||||
branchName,
|
||||
triggerUsername,
|
||||
errorDetails,
|
||||
} = input;
|
||||
|
||||
// Extract content from the original comment body
|
||||
@@ -177,7 +179,14 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
||||
}
|
||||
|
||||
// Build the new body with blank line between header and separator
|
||||
let newBody = `${header}${links}\n\n---\n`;
|
||||
let newBody = `${header}${links}`;
|
||||
|
||||
// Add error details if available
|
||||
if (actionFailed && errorDetails) {
|
||||
newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``;
|
||||
}
|
||||
|
||||
newBody += `\n\n---\n`;
|
||||
|
||||
// Clean up the body content
|
||||
// Remove any existing View job run, branch links from the bottom
|
||||
|
||||
@@ -39,6 +39,25 @@ describe("updateCommentBody", () => {
|
||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
||||
});
|
||||
|
||||
it("includes error details when provided", () => {
|
||||
const input = {
|
||||
...baseInput,
|
||||
currentBody: "Claude Code is working...",
|
||||
actionFailed: true,
|
||||
executionDetails: { duration_ms: 45000 },
|
||||
errorDetails: "Failed to fetch issue data",
|
||||
};
|
||||
|
||||
const result = updateCommentBody(input);
|
||||
expect(result).toContain("**Claude encountered an error after 45s**");
|
||||
expect(result).toContain("[View job]");
|
||||
expect(result).toContain("```\nFailed to fetch issue data\n```");
|
||||
// Ensure error details come after the header/links
|
||||
const errorIndex = result.indexOf("```");
|
||||
const headerIndex = result.indexOf("**Claude encountered an error");
|
||||
expect(errorIndex).toBeGreaterThan(headerIndex);
|
||||
});
|
||||
|
||||
it("handles username extraction from content when not provided", () => {
|
||||
const input = {
|
||||
...baseInput,
|
||||
|
||||
@@ -722,51 +722,4 @@ describe("buildDisallowedToolsString", () => {
|
||||
expect(parts).toContain("BadTool1");
|
||||
expect(parts).toContain("BadTool2");
|
||||
});
|
||||
|
||||
test("should remove hardcoded disallowed tools if they are in allowed tools", () => {
|
||||
const customDisallowedTools = "BadTool1,BadTool2";
|
||||
const allowedTools = "WebSearch,SomeOtherTool";
|
||||
const result = buildDisallowedToolsString(
|
||||
customDisallowedTools,
|
||||
allowedTools,
|
||||
);
|
||||
|
||||
// WebSearch should be removed from disallowed since it's in allowed
|
||||
expect(result).not.toContain("WebSearch");
|
||||
|
||||
// WebFetch should still be disallowed since it's not in allowed
|
||||
expect(result).toContain("WebFetch");
|
||||
|
||||
// Custom disallowed tools should still be present
|
||||
expect(result).toContain("BadTool1");
|
||||
expect(result).toContain("BadTool2");
|
||||
});
|
||||
|
||||
test("should remove all hardcoded disallowed tools if they are all in allowed tools", () => {
|
||||
const allowedTools = "WebSearch,WebFetch,SomeOtherTool";
|
||||
const result = buildDisallowedToolsString(undefined, allowedTools);
|
||||
|
||||
// Both hardcoded disallowed tools should be removed
|
||||
expect(result).not.toContain("WebSearch");
|
||||
expect(result).not.toContain("WebFetch");
|
||||
|
||||
// Result should be empty since no custom disallowed tools provided
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
test("should handle custom disallowed tools when all hardcoded tools are overridden", () => {
|
||||
const customDisallowedTools = "BadTool1,BadTool2";
|
||||
const allowedTools = "WebSearch,WebFetch";
|
||||
const result = buildDisallowedToolsString(
|
||||
customDisallowedTools,
|
||||
allowedTools,
|
||||
);
|
||||
|
||||
// Hardcoded tools should be removed
|
||||
expect(result).not.toContain("WebSearch");
|
||||
expect(result).not.toContain("WebFetch");
|
||||
|
||||
// Only custom disallowed tools should remain
|
||||
expect(result).toBe("BadTool1,BadTool2");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user