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 115 additions and 23 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 "" >> $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 "- **Actor**: @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "- **Triggered by**: $GITHUB_EVENT_NAME" >> $GITHUB_STEP_SUMMARY
echo "- **Actor**: @$GITHUB_ACTOR" >> $GITHUB_STEP_SUMMARY

View File

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

View File

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

View File

@@ -101,7 +101,7 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
env.GITHUB_ACTION_INPUTS = process.env.INPUT_ACTION_INPUTS_PRESENT;
}
// Build system prompt option
// Build system prompt option - default to claude_code preset
let systemPrompt: SdkOptions["systemPrompt"];
if (options.systemPrompt) {
systemPrompt = options.systemPrompt;
@@ -111,6 +111,12 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
preset: "claude_code",
append: options.appendSystemPrompt,
};
} else {
// Default to claude_code preset when no custom prompt is specified
systemPrompt = {
type: "preset",
preset: "claude_code",
};
}
// Build SDK options - use merged tools from both direct options and claudeArgs
@@ -130,6 +136,9 @@ export function parseSdkOptions(options: ClaudeOptions): ParsedSdkOptions {
// Note: allowedTools and disallowedTools have been removed from extraArgs to prevent duplicates
extraArgs,
env,
// Load settings from all sources to pick up CLI-installed plugins, CLAUDE.md, etc.
settingSources: ["user", "project", "local"],
};
return {

View File

@@ -75,6 +75,9 @@ export async function runClaudeWithSdk(
}
console.log(`Running Claude with prompt from file: ${promptPath}`);
// Log SDK options without env (which could contain sensitive data)
const { env, ...optionsToLog } = sdkOptions;
console.log("SDK options:", JSON.stringify(optionsToLog, null, 2));
const messages: SDKMessage[] = [];
let resultMessage: SDKResultMessage | undefined;

View File

@@ -15,6 +15,20 @@ import { GITHUB_SERVER_URL } from "../github/api/config";
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
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() {
try {
const commentId = parseInt(process.env.CLAUDE_COMMENT_ID!);
@@ -140,7 +154,7 @@ async function run() {
const prBody = encodeURIComponent(
`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})`;
}
} catch (error) {

View File

@@ -2,6 +2,20 @@ import type { Octokits } from "../api/client";
import { GITHUB_SERVER_URL } from "../api/config";
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(
octokit: Octokits,
owner: string,
@@ -80,7 +94,7 @@ export async function checkAndCommitOrDeleteBranch(
);
// 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})`;
} else {
console.log(
@@ -91,7 +105,7 @@ export async function checkAndCommitOrDeleteBranch(
} catch (gitError) {
console.error("Error checking/committing changes:", gitError);
// 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})`;
}
} else {
@@ -102,13 +116,13 @@ export async function checkAndCommitOrDeleteBranch(
}
} else {
// 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})`;
}
} catch (error) {
console.error("Error comparing commits on Claude branch:", error);
// 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})`;
}
}

View File

@@ -1,5 +1,19 @@
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 = {
total_cost_usd?: number;
duration_ms?: number;
@@ -160,7 +174,7 @@ export function updateCommentBody(input: CommentUpdateInput): string {
// Extract owner/repo from jobUrl
const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
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)
// Use greedy match with end anchor to capture full URL even if it contains parentheses
const prUrl =
prLinkFromContent || (prLink ? prLink.match(/\(([^)]+)\)/)?.[1] : "");
prLinkFromContent || (prLink ? prLink.match(/\((.+)\)$/)?.[1] : "");
if (prUrl) {
links += ` • [Create PR ➔](${prUrl})`;
}

View File

@@ -12,12 +12,26 @@ export function createJobRunLink(
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(
owner: string,
repo: string,
branchName: 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})`;
}

View File

@@ -139,6 +139,21 @@ describe("updateCommentBody", () => {
);
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", () => {