Compare commits

..

5 Commits

Author SHA1 Message Date
Claude
fb6c6b51f4 fix: Encode branch names in URLs to prevent truncation in markdown links
Branch names containing special characters (particularly parentheses)
were breaking markdown links in GitHub comments. This caused URLs to be
truncated when clicked.

Changes:
- Add encodeBranchName() helper function that:
  - Uses encodeURIComponent for basic encoding
  - Preserves forward slashes (GitHub expects literal / in branch URLs)
  - Manually encodes parentheses (not encoded by encodeURIComponent per RFC 3986)
- Apply encoding to branch URLs in:
  - update-comment-link.ts (PR compare URLs)
  - branch-cleanup.ts (branch tree URLs)
  - comment-logic.ts (branch tree URLs)
  - comments/common.ts (branch tree URLs)
- Improve PR link regex to use greedy match with end anchor
- Add test for branch names with special characters
2025-12-09 06:34:34 +00:00
GitHub Actions
f0c8eb2980 chore: bump Claude Code version to 2.0.62 2025-12-09 02:12:14 +00:00
ant-soumitr
68a0348c20 fix: Replace direct template expansion of inputs in shell scripts with environment variables (#729)
Replace direct template expansion of user inputs in shell scripts with
environment variables to prevent potential command injection attacks.

Changes:
- sync-base-action.yml: Use $GITHUB_EVENT_NAME and $GITHUB_ACTOR instead of template expansion
- action.yml: Pass path_to_bun_executable and path_to_claude_code_executable through env vars
- base-action/action.yml: Same env var changes for path inputs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-08 12:08:44 -08:00
GitHub Actions
dc06a34646 chore: bump Claude Code version to 2.0.61 2025-12-07 10:47:47 +00:00
Ashwin Bhat
a3bb51dac1 Fix SDK path: add settingSources and default system prompt (#726)
Two fixes for the Agent SDK path (USE_AGENT_SDK=true):

1. Add settingSources to load filesystem settings
   - Without this, CLI-installed plugins aren't available to the SDK
   - Also needed to load CLAUDE.md files from the project

2. Default systemPrompt to claude_code preset
   - Without an explicit systemPrompt, the SDK would use no system prompt
   - Now defaults to { type: "preset", preset: "claude_code" } to match CLI behavior

Also adds logging of SDK options (excluding env) for debugging.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-06 16:52:26 -08:00
10 changed files with 105 additions and 54 deletions

View File

@@ -94,5 +94,5 @@ jobs:
echo "✅ Successfully synced \`base-action\` directory to [anthropics/claude-code-base-action](https://github.com/anthropics/claude-code-base-action)" >> $GITHUB_STEP_SUMMARY echo "✅ Successfully synced \`base-action\` directory to [anthropics/claude-code-base-action](https://github.com/anthropics/claude-code-base-action)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Source commit**: [\`${GITHUB_SHA:0:7}\`](https://github.com/anthropics/claude-code-action/commit/${GITHUB_SHA})" >> $GITHUB_STEP_SUMMARY echo "- **Source commit**: [\`${GITHUB_SHA:0:7}\`](https://github.com/anthropics/claude-code-action/commit/${GITHUB_SHA})" >> $GITHUB_STEP_SUMMARY
echo "- **Triggered by**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY echo "- **Triggered by**: $GITHUB_EVENT_NAME" >> $GITHUB_STEP_SUMMARY
echo "- **Actor**: @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY echo "- **Actor**: @$GITHUB_ACTOR" >> $GITHUB_STEP_SUMMARY

View File

@@ -140,10 +140,12 @@ runs:
- name: Setup Custom Bun Path - name: Setup Custom Bun Path
if: inputs.path_to_bun_executable != '' if: inputs.path_to_bun_executable != ''
shell: bash shell: bash
env:
PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
run: | run: |
echo "Using custom Bun executable: ${{ inputs.path_to_bun_executable }}" echo "Using custom Bun executable: $PATH_TO_BUN_EXECUTABLE"
# Add the directory containing the custom executable to PATH # Add the directory containing the custom executable to PATH
BUN_DIR=$(dirname "${{ inputs.path_to_bun_executable }}") BUN_DIR=$(dirname "$PATH_TO_BUN_EXECUTABLE")
echo "$BUN_DIR" >> "$GITHUB_PATH" echo "$BUN_DIR" >> "$GITHUB_PATH"
- name: Install Dependencies - name: Install Dependencies
@@ -182,6 +184,8 @@ runs:
- name: Install Base Action Dependencies - name: Install Base Action Dependencies
if: steps.prepare.outputs.contains_trigger == 'true' if: steps.prepare.outputs.contains_trigger == 'true'
shell: bash shell: bash
env:
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
run: | run: |
echo "Installing base-action dependencies..." echo "Installing base-action dependencies..."
cd ${GITHUB_ACTION_PATH}/base-action cd ${GITHUB_ACTION_PATH}/base-action
@@ -190,8 +194,8 @@ runs:
cd - cd -
# Install Claude Code if no custom executable is provided # Install Claude Code if no custom executable is provided
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.0.60" CLAUDE_CODE_VERSION="2.0.62"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..." echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do for attempt in 1 2 3; do
echo "Installation attempt $attempt..." echo "Installation attempt $attempt..."
@@ -210,9 +214,9 @@ runs:
echo "Claude Code installed successfully" echo "Claude Code installed successfully"
echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
else else
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}" echo "Using custom Claude Code executable: $PATH_TO_CLAUDE_CODE_EXECUTABLE"
# Add the directory containing the custom executable to PATH # Add the directory containing the custom executable to PATH
CLAUDE_DIR=$(dirname "${{ inputs.path_to_claude_code_executable }}") CLAUDE_DIR=$(dirname "$PATH_TO_CLAUDE_CODE_EXECUTABLE")
echo "$CLAUDE_DIR" >> "$GITHUB_PATH" echo "$CLAUDE_DIR" >> "$GITHUB_PATH"
fi fi

View File

@@ -101,10 +101,12 @@ runs:
- name: Setup Custom Bun Path - name: Setup Custom Bun Path
if: inputs.path_to_bun_executable != '' if: inputs.path_to_bun_executable != ''
shell: bash shell: bash
env:
PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
run: | run: |
echo "Using custom Bun executable: ${{ inputs.path_to_bun_executable }}" echo "Using custom Bun executable: $PATH_TO_BUN_EXECUTABLE"
# Add the directory containing the custom executable to PATH # Add the directory containing the custom executable to PATH
BUN_DIR=$(dirname "${{ inputs.path_to_bun_executable }}") BUN_DIR=$(dirname "$PATH_TO_BUN_EXECUTABLE")
echo "$BUN_DIR" >> "$GITHUB_PATH" echo "$BUN_DIR" >> "$GITHUB_PATH"
- name: Install Dependencies - name: Install Dependencies
@@ -115,9 +117,11 @@ runs:
- name: Install Claude Code - name: Install Claude Code
shell: bash shell: bash
env:
PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
run: | run: |
if [ -z "${{ inputs.path_to_claude_code_executable }}" ]; then if [ -z "$PATH_TO_CLAUDE_CODE_EXECUTABLE" ]; then
CLAUDE_CODE_VERSION="2.0.60" CLAUDE_CODE_VERSION="2.0.62"
echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..." echo "Installing Claude Code v${CLAUDE_CODE_VERSION}..."
for attempt in 1 2 3; do for attempt in 1 2 3; do
echo "Installation attempt $attempt..." echo "Installation attempt $attempt..."
@@ -135,9 +139,9 @@ runs:
done done
echo "Claude Code installed successfully" echo "Claude Code installed successfully"
else else
echo "Using custom Claude Code executable: ${{ inputs.path_to_claude_code_executable }}" echo "Using custom Claude Code executable: $PATH_TO_CLAUDE_CODE_EXECUTABLE"
# Add the directory containing the custom executable to PATH # Add the directory containing the custom executable to PATH
CLAUDE_DIR=$(dirname "${{ inputs.path_to_claude_code_executable }}") CLAUDE_DIR=$(dirname "$PATH_TO_CLAUDE_CODE_EXECUTABLE")
echo "$CLAUDE_DIR" >> "$GITHUB_PATH" echo "$CLAUDE_DIR" >> "$GITHUB_PATH"
fi fi

View File

@@ -75,28 +75,15 @@ export async function runClaudeWithSdk(
} }
console.log(`Running Claude with prompt from file: ${promptPath}`); console.log(`Running Claude with prompt from file: ${promptPath}`);
console.log( // Log SDK options without env (which could contain sensitive data)
"[DEBUG] Prompt content (first 2000 chars):", const { env, ...optionsToLog } = sdkOptions;
prompt.substring(0, 2000), console.log("SDK options:", JSON.stringify(optionsToLog, null, 2));
);
console.log("[DEBUG] Prompt length:", prompt.length);
console.log(
"[DEBUG] sdkOptions passed to query():",
JSON.stringify(sdkOptions, null, 2),
);
const messages: SDKMessage[] = []; const messages: SDKMessage[] = [];
let resultMessage: SDKResultMessage | undefined; let resultMessage: SDKResultMessage | undefined;
try { try {
console.log("[DEBUG] About to call query()...");
for await (const message of query({ prompt, options: sdkOptions })) { for await (const message of query({ prompt, options: sdkOptions })) {
console.log(
"[DEBUG] Received message type:",
message.type,
"subtype:",
(message as any).subtype,
);
messages.push(message); messages.push(message);
const sanitized = sanitizeSdkOutput(message, showFullOutput); const sanitized = sanitizeSdkOutput(message, showFullOutput);
@@ -106,16 +93,8 @@ export async function runClaudeWithSdk(
if (message.type === "result") { if (message.type === "result") {
resultMessage = message as SDKResultMessage; resultMessage = message as SDKResultMessage;
console.log(
"[DEBUG] Got result message:",
JSON.stringify(resultMessage, null, 2),
);
} }
} }
console.log(
"[DEBUG] Finished iterating query(), total messages:",
messages.length,
);
} catch (error) { } catch (error) {
console.error("SDK execution error:", error); console.error("SDK execution error:", error);
core.setOutput("conclusion", "failure"); core.setOutput("conclusion", "failure");

View File

@@ -174,15 +174,7 @@ export async function runClaude(promptPath: string, options: ClaudeOptions) {
); );
if (useAgentSdk) { if (useAgentSdk) {
console.log(
"[DEBUG] Raw options passed to SDK path:",
JSON.stringify(options, null, 2),
);
const parsedOptions = parseSdkOptions(options); const parsedOptions = parseSdkOptions(options);
console.log(
"[DEBUG] Parsed SDK options:",
JSON.stringify(parsedOptions, null, 2),
);
return runClaudeWithSdk(promptPath, parsedOptions); return runClaudeWithSdk(promptPath, parsedOptions);
} }

View File

@@ -15,6 +15,20 @@ import { GITHUB_SERVER_URL } from "../github/api/config";
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup"; import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment"; import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
/**
* Encodes a branch name for use in a URL, preserving forward slashes.
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
* but other special characters like parentheses need to be encoded.
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
* but parentheses break markdown links so we encode them manually.
*/
function encodeBranchName(branchName: string): string {
return encodeURIComponent(branchName)
.replace(/%2F/gi, "/")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29");
}
async function run() { async function run() {
try { try {
const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!); const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!);
@@ -140,7 +154,7 @@ async function run() {
const prBody = encodeURIComponent( const prBody = encodeURIComponent(
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`, `This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
); );
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; const prUrl = `${serverUrl}/${owner}/${repo}/compare/${encodeBranchName(baseBranch)}...${encodeBranchName(claudeBranch)}?quick_pull=1&title=${prTitle}&body=${prBody}`;
prLink = `\n[Create a PR](${prUrl})`; prLink = `\n[Create a PR](${prUrl})`;
} }
} catch (error) { } catch (error) {

View File

@@ -2,6 +2,20 @@ import type { Octokits } from "../api/client";
import { GITHUB_SERVER_URL } from "../api/config"; import { GITHUB_SERVER_URL } from "../api/config";
import { $ } from "bun"; import { $ } from "bun";
/**
* Encodes a branch name for use in a URL, preserving forward slashes.
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
* but other special characters like parentheses need to be encoded.
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
* but parentheses break markdown links so we encode them manually.
*/
function encodeBranchName(branchName: string): string {
return encodeURIComponent(branchName)
.replace(/%2F/gi, "/")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29");
}
export async function checkAndCommitOrDeleteBranch( export async function checkAndCommitOrDeleteBranch(
octokit: Octokits, octokit: Octokits,
owner: string, owner: string,
@@ -80,7 +94,7 @@ export async function checkAndCommitOrDeleteBranch(
); );
// Set branch link since we now have commits // Set branch link since we now have commits
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
branchLink = `\n[View branch](${branchUrl})`; branchLink = `\n[View branch](${branchUrl})`;
} else { } else {
console.log( console.log(
@@ -91,7 +105,7 @@ export async function checkAndCommitOrDeleteBranch(
} catch (gitError) { } catch (gitError) {
console.error("Error checking/committing changes:", gitError); console.error("Error checking/committing changes:", gitError);
// If we can't check git status, assume the branch might have changes // If we can't check git status, assume the branch might have changes
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
branchLink = `\n[View branch](${branchUrl})`; branchLink = `\n[View branch](${branchUrl})`;
} }
} else { } else {
@@ -102,13 +116,13 @@ export async function checkAndCommitOrDeleteBranch(
} }
} else { } else {
// Only add branch link if there are commits // Only add branch link if there are commits
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
branchLink = `\n[View branch](${branchUrl})`; branchLink = `\n[View branch](${branchUrl})`;
} }
} catch (error) { } catch (error) {
console.error("Error comparing commits on Claude branch:", error); console.error("Error comparing commits on Claude branch:", error);
// If we can't compare but the branch exists remotely, include the branch link // If we can't compare but the branch exists remotely, include the branch link
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(claudeBranch)}`;
branchLink = `\n[View branch](${branchUrl})`; branchLink = `\n[View branch](${branchUrl})`;
} }
} }

View File

@@ -1,5 +1,19 @@
import { GITHUB_SERVER_URL } from "../api/config"; import { GITHUB_SERVER_URL } from "../api/config";
/**
* Encodes a branch name for use in a URL, preserving forward slashes.
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
* but other special characters like parentheses need to be encoded.
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
* but parentheses break markdown links so we encode them manually.
*/
function encodeBranchName(branchName: string): string {
return encodeURIComponent(branchName)
.replace(/%2F/gi, "/")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29");
}
export type ExecutionDetails = { export type ExecutionDetails = {
total_cost_usd?: number; total_cost_usd?: number;
duration_ms?: number; duration_ms?: number;
@@ -160,7 +174,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
// Extract owner/repo from jobUrl // Extract owner/repo from jobUrl
const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//); const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
if (repoMatch) { if (repoMatch) {
branchUrl = `${GITHUB_SERVER_URL}/${repoMatch[1]}/${repoMatch[2]}/tree/${finalBranchName}`; branchUrl = `${GITHUB_SERVER_URL}/${repoMatch[1]}/${repoMatch[2]}/tree/${encodeBranchName(finalBranchName)}`;
} }
} }
@@ -172,8 +186,9 @@ export function updateCommentBody(input: CommentUpdateInput): string {
} }
// Add PR link (either from content or provided) // Add PR link (either from content or provided)
// Use greedy match with end anchor to capture full URL even if it contains parentheses
const prUrl = const prUrl =
prLinkFromContent || (prLink ? prLink.match(/\(([^)]+)\)/)?.[1] : ""); prLinkFromContent || (prLink ? prLink.match(/\((.+)\)$/)?.[1] : "");
if (prUrl) { if (prUrl) {
links += ` • [Create PR ➔](${prUrl})`; links += ` • [Create PR ➔](${prUrl})`;
} }

View File

@@ -12,12 +12,26 @@ export function createJobRunLink(
return `[View job run](${jobRunUrl})`; return `[View job run](${jobRunUrl})`;
} }
/**
* Encodes a branch name for use in a URL, preserving forward slashes.
* GitHub expects literal slashes in branch names (e.g., /tree/feature/branch)
* but other special characters like parentheses need to be encoded.
* Note: encodeURIComponent doesn't encode ( ) ! ' * ~ per RFC 3986,
* but parentheses break markdown links so we encode them manually.
*/
function encodeBranchName(branchName: string): string {
return encodeURIComponent(branchName)
.replace(/%2F/gi, "/")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29");
}
export function createBranchLink( export function createBranchLink(
owner: string, owner: string,
repo: string, repo: string,
branchName: string, branchName: string,
): string { ): string {
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${branchName}`; const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${encodeBranchName(branchName)}`;
return `\n[View branch](${branchUrl})`; return `\n[View branch](${branchUrl})`;
} }

View File

@@ -139,6 +139,21 @@ describe("updateCommentBody", () => {
); );
expect(result).not.toContain("View branch"); expect(result).not.toContain("View branch");
}); });
it("encodes special characters in branch names while preserving slashes", () => {
const input = {
...baseInput,
branchName: "feature/fix(issue)-test",
};
const result = updateCommentBody(input);
// Branch name display should show the original name
expect(result).toContain("`feature/fix(issue)-test`");
// URL should have encoded parentheses but preserved slashes
expect(result).toContain(
"https://github.com/owner/repo/tree/feature/fix%28issue%29-test",
);
});
}); });
describe("PR link", () => { describe("PR link", () => {